sass 3.3.14 → 3.4.0.rc.1

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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +5 -5
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +0 -5
  10. data/lib/sass/css.rb +1 -3
  11. data/lib/sass/engine.rb +28 -39
  12. data/lib/sass/environment.rb +13 -17
  13. data/lib/sass/error.rb +6 -9
  14. data/lib/sass/exec.rb +5 -771
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +419 -0
  18. data/lib/sass/features.rb +6 -0
  19. data/lib/sass/importers.rb +0 -1
  20. data/lib/sass/importers/base.rb +5 -1
  21. data/lib/sass/importers/filesystem.rb +4 -21
  22. data/lib/sass/media.rb +1 -4
  23. data/lib/sass/plugin/compiler.rb +32 -136
  24. data/lib/sass/script/css_lexer.rb +1 -1
  25. data/lib/sass/script/functions.rb +363 -39
  26. data/lib/sass/script/lexer.rb +68 -50
  27. data/lib/sass/script/parser.rb +29 -14
  28. data/lib/sass/script/tree.rb +1 -0
  29. data/lib/sass/script/tree/funcall.rb +1 -1
  30. data/lib/sass/script/tree/interpolation.rb +19 -1
  31. data/lib/sass/script/tree/selector.rb +26 -0
  32. data/lib/sass/script/value.rb +0 -1
  33. data/lib/sass/script/value/bool.rb +0 -5
  34. data/lib/sass/script/value/color.rb +32 -12
  35. data/lib/sass/script/value/helpers.rb +107 -0
  36. data/lib/sass/script/value/list.rb +0 -15
  37. data/lib/sass/script/value/null.rb +0 -5
  38. data/lib/sass/script/value/number.rb +60 -14
  39. data/lib/sass/script/value/string.rb +53 -9
  40. data/lib/sass/scss/css_parser.rb +8 -2
  41. data/lib/sass/scss/parser.rb +175 -319
  42. data/lib/sass/scss/rx.rb +14 -5
  43. data/lib/sass/scss/static_parser.rb +298 -1
  44. data/lib/sass/selector.rb +56 -193
  45. data/lib/sass/selector/abstract_sequence.rb +28 -13
  46. data/lib/sass/selector/comma_sequence.rb +91 -12
  47. data/lib/sass/selector/pseudo.rb +256 -0
  48. data/lib/sass/selector/sequence.rb +99 -31
  49. data/lib/sass/selector/simple.rb +14 -25
  50. data/lib/sass/selector/simple_sequence.rb +101 -37
  51. data/lib/sass/shared.rb +1 -1
  52. data/lib/sass/source/map.rb +23 -9
  53. data/lib/sass/stack.rb +0 -6
  54. data/lib/sass/supports.rb +1 -1
  55. data/lib/sass/tree/at_root_node.rb +1 -0
  56. data/lib/sass/tree/directive_node.rb +7 -1
  57. data/lib/sass/tree/error_node.rb +18 -0
  58. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  59. data/lib/sass/tree/prop_node.rb +1 -1
  60. data/lib/sass/tree/rule_node.rb +11 -6
  61. data/lib/sass/tree/visitors/check_nesting.rb +3 -4
  62. data/lib/sass/tree/visitors/convert.rb +8 -17
  63. data/lib/sass/tree/visitors/cssize.rb +12 -24
  64. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  65. data/lib/sass/tree/visitors/perform.rb +43 -28
  66. data/lib/sass/tree/visitors/set_options.rb +5 -0
  67. data/lib/sass/tree/visitors/to_css.rb +14 -13
  68. data/lib/sass/util.rb +94 -90
  69. data/test/sass/cache_test.rb +1 -1
  70. data/test/sass/callbacks_test.rb +1 -1
  71. data/test/sass/compiler_test.rb +5 -14
  72. data/test/sass/conversion_test.rb +47 -1
  73. data/test/sass/css2sass_test.rb +3 -3
  74. data/test/sass/encoding_test.rb +219 -0
  75. data/test/sass/engine_test.rb +128 -191
  76. data/test/sass/exec_test.rb +2 -2
  77. data/test/sass/extend_test.rb +234 -17
  78. data/test/sass/functions_test.rb +268 -213
  79. data/test/sass/importer_test.rb +31 -21
  80. data/test/sass/logger_test.rb +1 -1
  81. data/test/sass/more_results/more_import.css +1 -1
  82. data/test/sass/plugin_test.rb +12 -11
  83. data/test/sass/results/compact.css +1 -1
  84. data/test/sass/results/complex.css +4 -4
  85. data/test/sass/results/expanded.css +1 -1
  86. data/test/sass/results/import.css +1 -1
  87. data/test/sass/results/import_charset_ibm866.css +2 -2
  88. data/test/sass/results/mixins.css +17 -17
  89. data/test/sass/results/nested.css +1 -1
  90. data/test/sass/results/parent_ref.css +2 -2
  91. data/test/sass/results/script.css +3 -3
  92. data/test/sass/results/scss_import.css +1 -1
  93. data/test/sass/script_conversion_test.rb +7 -4
  94. data/test/sass/script_test.rb +202 -79
  95. data/test/sass/scss/css_test.rb +95 -25
  96. data/test/sass/scss/rx_test.rb +4 -4
  97. data/test/sass/scss/scss_test.rb +363 -19
  98. data/test/sass/source_map_test.rb +48 -41
  99. data/test/sass/superselector_test.rb +191 -0
  100. data/test/sass/templates/scss_import.scss +2 -1
  101. data/test/sass/test_helper.rb +1 -1
  102. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  103. data/test/sass/util/normalized_map_test.rb +1 -1
  104. data/test/sass/util/subset_map_test.rb +2 -2
  105. data/test/sass/util_test.rb +1 -1
  106. data/test/sass/value_helpers_test.rb +3 -3
  107. data/test/test_helper.rb +2 -2
  108. metadata +30 -7
  109. data/lib/sass/importers/deprecated_path.rb +0 -51
  110. data/lib/sass/script/value/deprecated_false.rb +0 -55
@@ -0,0 +1,15 @@
1
+ module Sass::Tree
2
+ class KeyframeRuleNode < Node
3
+ # The text of the directive after any interpolated SassScript has been resolved.
4
+ # Since this is only a static node, this is the only value property.
5
+ #
6
+ # @return [String]
7
+ attr_accessor :resolved_value
8
+
9
+ # @param resolved_value [String] See \{#resolved_value}
10
+ def initialize(resolved_value)
11
+ @resolved_value = resolved_value
12
+ super()
13
+ end
14
+ end
15
+ end
@@ -97,7 +97,7 @@ module Sass::Tree
97
97
  # @param opts [{Symbol => Object}] The options hash for the tree.
98
98
  # @param fmt [Symbol] `:scss` or `:sass`.
99
99
  def declaration(opts = {:old => @prop_syntax == :old}, fmt = :sass)
100
- name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass(opts)}}"}.join
100
+ name = self.name.map {|n| n.is_a?(String) ? n : n.to_sass(opts)}.join
101
101
  if name[0] == ?:
102
102
  raise Sass::SyntaxError.new("The \"#{name}: #{self.class.val_to_sass(value, opts)}\"" +
103
103
  " hack is not allowed in the Sass indented syntax")
@@ -60,15 +60,20 @@ module Sass::Tree
60
60
  # @return [String]
61
61
  attr_accessor :stack_trace
62
62
 
63
- # @param rule [Array<String, Sass::Script::Tree::Node>]
63
+ # @param rule [Array<String, Sass::Script::Tree::Node>, Sass::Selector::CommaSequence]
64
+ # The CSS rule, either unparsed or parsed.
64
65
  # @param selector_source_range [Sass::Source::Range]
65
- # The CSS rule. See \{#rule}
66
66
  def initialize(rule, selector_source_range = nil)
67
- merged = Sass::Util.merge_adjacent_strings(rule)
68
- @rule = Sass::Util.strip_string_array(merged)
67
+ if rule.is_a?(Sass::Selector::CommaSequence)
68
+ @rule = [rule.to_s]
69
+ @parsed_rules = rule
70
+ else
71
+ merged = Sass::Util.merge_adjacent_strings(rule)
72
+ @rule = Sass::Util.strip_string_array(merged)
73
+ try_to_parse_non_interpolated_rules
74
+ end
69
75
  @selector_source_range = selector_source_range
70
76
  @tabs = 0
71
- try_to_parse_non_interpolated_rules
72
77
  super()
73
78
  end
74
79
 
@@ -130,7 +135,7 @@ module Sass::Tree
130
135
  if @rule.all? {|t| t.kind_of?(String)}
131
136
  # We don't use real filename/line info because we don't have it yet.
132
137
  # When we get it, we'll set it on the parsed rules if possible.
133
- parser = Sass::SCSS::StaticParser.new(@rule.join.strip, '', nil, 1)
138
+ parser = Sass::SCSS::StaticParser.new(@rule.join.strip, nil, nil, 1)
134
139
  # rubocop:disable RescueModifier
135
140
  @parsed_rules = parser.parse_selector rescue nil
136
141
  # rubocop:enable RescueModifier
@@ -123,7 +123,7 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
123
123
 
124
124
  VALID_FUNCTION_CHILDREN = [
125
125
  Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
126
- Sass::Tree::VariableNode, Sass::Tree::WarnNode
126
+ Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::ErrorNode
127
127
  ] + CONTROL_NODES
128
128
  def invalid_function_child?(parent, child)
129
129
  unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
@@ -140,9 +140,8 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
140
140
  end
141
141
  end
142
142
 
143
- VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::PropNode,
144
- Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode,
145
- Sass::Tree::MixinNode]
143
+ VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::KeyframeRuleNode, Sass::Tree::PropNode,
144
+ Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode, Sass::Tree::MixinNode]
146
145
  def invalid_prop_parent?(parent, child)
147
146
  unless is_any_of?(parent, VALID_PROP_PARENTS)
148
147
  "Properties are only allowed within rules, directives, mixin includes, or other properties." +
@@ -96,6 +96,10 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
96
96
  "#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"
97
97
  end
98
98
 
99
+ def visit_error(node)
100
+ "#{tab_str}@error #{node.expr.to_sass(@options)}#{semi}\n"
101
+ end
102
+
99
103
  def visit_directive(node)
100
104
  res = "#{tab_str}#{interp_to_src(node.value)}"
101
105
  res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2')
@@ -236,7 +240,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
236
240
  end
237
241
 
238
242
  def visit_rule(node)
239
- rule = node.parsed_rules ? node.parsed_rules.to_a : node.rule
243
+ rule = node.parsed_rules ? [node.parsed_rules.to_s] : node.rule
240
244
  if @format == :sass
241
245
  name = selector_to_sass(rule)
242
246
  name = "\\" + name if name[0] == ?:
@@ -279,10 +283,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
279
283
  private
280
284
 
281
285
  def interp_to_src(interp)
282
- interp.map do |r|
283
- next r if r.is_a?(String)
284
- "\#{#{r.to_sass(@options)}}"
285
- end.join
286
+ interp.map {|r| r.is_a?(String) ? r : r.to_sass(@options)}.join
286
287
  end
287
288
 
288
289
  # Like interp_to_src, but removes the unnecessary `#{}` around the keys and
@@ -294,17 +295,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
294
295
  e.value.value
295
296
  end
296
297
 
297
- Sass::Util.enum_with_index(interp).map do |r, i|
298
- next r if r.is_a?(String)
299
- before, after = interp[i - 1], interp[i + 1]
300
- if before.is_a?(String) && after.is_a?(String) &&
301
- ((before[-1] == ?( && after[0] == ?:) ||
302
- (before =~ /:\s*/ && after[0] == ?)))
303
- r.to_sass(@options)
304
- else
305
- "\#{#{r.to_sass(@options)}}"
306
- end
307
- end.join
298
+ interp_to_src(interp)
308
299
  end
309
300
 
310
301
  def selector_to_src(sel)
@@ -316,7 +307,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
316
307
  if r.is_a?(String)
317
308
  r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
318
309
  else
319
- "\#{#{r.to_sass(@options)}}"
310
+ r.to_sass(@options)
320
311
  end
321
312
  end.join
322
313
  end
@@ -123,29 +123,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
123
123
 
124
124
  # Registers an extension in the `@extends` subset map.
125
125
  def visit_extend(node)
126
- node.resolved_selector.members.each do |seq|
127
- if seq.members.size > 1
128
- raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
129
- end
130
-
131
- sseq = seq.members.first
132
- if !sseq.is_a?(Sass::Selector::SimpleSequence)
133
- raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
134
- elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
135
- raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
136
- end
137
-
138
- sel = sseq.members
139
- parent.resolved_rules.members.each do |member|
140
- unless member.members.last.is_a?(Sass::Selector::SimpleSequence)
141
- raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
142
- end
143
-
144
- parent_directives = @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)}
145
- @extends[sel] = Extend.new(member, sel, node, parent_directives, :not_found)
146
- end
147
- end
148
-
126
+ parent.resolved_rules.populate_extends(@extends, node.resolved_selector, node,
127
+ @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)})
149
128
  []
150
129
  end
151
130
 
@@ -232,6 +211,14 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
232
211
  rules
233
212
  end
234
213
 
214
+ def visit_keyframerule(node)
215
+ return node unless node.has_children
216
+
217
+ yield
218
+
219
+ debubble(node.children, node)
220
+ end
221
+
235
222
  # Bubbles a directive up through RuleNodes.
236
223
  def visit_directive(node)
237
224
  return node unless node.has_children
@@ -249,7 +236,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
249
236
  child.node.resolved_value == node.resolved_value
250
237
  end
251
238
 
252
- if directive_exists
239
+ # We know empty @keyframes directives do nothing.
240
+ if directive_exists || node.name == '@keyframes'
253
241
  []
254
242
  else
255
243
  empty_node = node.dup
@@ -16,6 +16,11 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
16
16
  yield
17
17
  end
18
18
 
19
+ def visit_error(node)
20
+ node.expr = node.expr.deep_copy
21
+ yield
22
+ end
23
+
19
24
  def visit_each(node)
20
25
  node.list = node.list.deep_copy
21
26
  yield
@@ -207,6 +207,17 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
207
207
  []
208
208
  end
209
209
 
210
+ # Throws the expression as an error.
211
+ def visit_error(node)
212
+ res = node.expr.perform(@environment)
213
+ if res.is_a?(Sass::Script::Value::String)
214
+ res = res.value
215
+ else
216
+ res = res.to_sass
217
+ end
218
+ raise Sass::SyntaxError.new(res)
219
+ end
220
+
210
221
  # Runs the child nodes once for each value in the list.
211
222
  def visit_each(node)
212
223
  list = node.list.perform(@environment)
@@ -377,18 +388,31 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
377
388
  # Runs SassScript interpolation in the selector,
378
389
  # and then parses the result into a {Sass::Selector::CommaSequence}.
379
390
  def visit_rule(node)
380
- old_at_root_without_rule, @at_root_without_rule = @at_root_without_rule, false
391
+ old_at_root_without_rule = @at_root_without_rule
381
392
  parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
382
393
  node.filename, node.options[:importer], node.line)
383
- node.parsed_rules ||= parser.parse_selector
384
- node.resolved_rules = node.parsed_rules.resolve_parent_refs(
385
- @environment.selector, !old_at_root_without_rule)
386
- node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
387
- with_environment Sass::Environment.new(@environment, node.options) do
388
- @environment.selector = node.resolved_rules
389
- node.children = node.children.map {|c| visit(c)}.flatten
394
+ if @in_keyframes
395
+ keyframe_rule_node = Sass::Tree::KeyframeRuleNode.new(parser.parse_keyframes_selector)
396
+ keyframe_rule_node.options = node.options
397
+ keyframe_rule_node.line = node.line
398
+ keyframe_rule_node.filename = node.filename
399
+ keyframe_rule_node.source_range = node.source_range
400
+ with_environment Sass::Environment.new(@environment, node.options) do
401
+ keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten
402
+ end
403
+ keyframe_rule_node
404
+ else
405
+ @at_root_without_rule = false
406
+ node.parsed_rules ||= parser.parse_selector
407
+ node.resolved_rules = node.parsed_rules.resolve_parent_refs(
408
+ @environment.selector, !old_at_root_without_rule)
409
+ node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
410
+ with_environment Sass::Environment.new(@environment, node.options) do
411
+ @environment.selector = node.resolved_rules
412
+ node.children = node.children.map {|c| visit(c)}.flatten
413
+ end
414
+ node
390
415
  end
391
- node
392
416
  ensure
393
417
  @at_root_without_rule = old_at_root_without_rule
394
418
  end
@@ -405,36 +429,24 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
405
429
  end
406
430
 
407
431
  old_at_root_without_rule = @at_root_without_rule
432
+ old_in_keyframes = @in_keyframes
408
433
  @at_root_without_rule = true if node.exclude?('rule')
434
+ @in_keyframes = false if node.exclude?('keyframes')
409
435
  yield
410
436
  ensure
437
+ @in_keyframes = old_in_keyframes
411
438
  @at_root_without_rule = old_at_root_without_rule
412
439
  end
413
440
 
414
441
  # Loads the new variable value into the environment.
415
442
  def visit_variable(node)
416
443
  env = @environment
417
- identifier = [node.name, node.filename, node.line]
418
- if node.global
419
- env = env.global_env
420
- elsif env.parent && env.is_var_global?(node.name) &&
421
- !env.global_env.global_warning_given.include?(identifier)
422
- env.global_env.global_warning_given.add(identifier)
423
- var_expr = "$#{node.name}: #{node.expr.to_sass(env.options)} !global"
424
- var_expr << " !default" if node.guarded
425
- location = "on line #{node.line}"
426
- location << " of #{node.filename}" if node.filename
427
- Sass::Util.sass_warn <<WARNING
428
- DEPRECATION WARNING #{location}:
429
- Assigning to global variable "$#{node.name}" by default is deprecated.
430
- In future versions of Sass, this will create a new local variable.
431
- If you want to assign to the global variable, use "#{var_expr}" instead.
432
- Note that this will be incompatible with Sass 3.2.
433
- WARNING
444
+ env = env.global_env if node.global
445
+ if node.guarded
446
+ var = env.var(node.name)
447
+ return [] if var && !var.null?
434
448
  end
435
449
 
436
- var = env.var(node.name)
437
- return [] if node.guarded && var && !var.null?
438
450
  val = node.expr.perform(@environment)
439
451
  if node.expr.source_range
440
452
  val.source_range = node.expr.source_range
@@ -466,10 +478,13 @@ WARNING
466
478
 
467
479
  def visit_directive(node)
468
480
  node.resolved_value = run_interp(node.value)
481
+ old_in_keyframes, @in_keyframes = @in_keyframes, node.normalized_name == "@keyframes"
469
482
  with_environment Sass::Environment.new(@environment) do
470
483
  node.children = node.children.map {|c| visit(c)}.flatten
471
484
  node
472
485
  end
486
+ ensure
487
+ @in_keyframes = old_in_keyframes
473
488
  end
474
489
 
475
490
  def visit_media(node)
@@ -25,6 +25,11 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
25
25
  yield
26
26
  end
27
27
 
28
+ def visit_error(node)
29
+ node.expr.options = @options
30
+ yield
31
+ end
32
+
28
33
  def visit_each(node)
29
34
  node.list.options = @options
30
35
  yield
@@ -127,21 +127,18 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
127
127
  return "" if @result.empty?
128
128
 
129
129
  output "\n"
130
- return @result if Sass::Util.ruby1_8? || @result.ascii_only?
131
-
132
- if node.children.first.is_a?(Sass::Tree::CharsetNode)
133
- begin
134
- encoding = node.children.first.name
135
- # Default to big-endian encoding, because we have to decide somehow
136
- encoding << 'BE' if encoding =~ /\Autf-(16|32)\Z/i
137
- @result = @result.encode(Encoding.find(encoding))
138
- rescue EncodingError
130
+
131
+ unless Sass::Util.ruby1_8? || @result.ascii_only?
132
+ if node.style == :compressed
133
+ # A byte order mark is sufficient to tell browsers that this
134
+ # file is UTF-8 encoded, and will override any other detection
135
+ # methods as per http://encoding.spec.whatwg.org/#decode-and-encode.
136
+ prepend! "\uFEFF"
137
+ else
138
+ prepend! "@charset \"UTF-8\";\n"
139
139
  end
140
140
  end
141
141
 
142
- prepend! "@charset \"#{@result.encoding.name}\";#{
143
- node.style == :compressed ? '' : "\n"
144
- }".encode(@result.encoding)
145
142
  @result
146
143
  rescue Sass::SyntaxError => e
147
144
  e.sass_template ||= node.template
@@ -281,7 +278,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
281
278
 
282
279
  joined_rules = node.resolved_rules.members.map do |seq|
283
280
  next if seq.has_placeholder?
284
- rule_part = seq.to_a.join
281
+ rule_part = seq.to_s
285
282
  if node.style == :compressed
286
283
  rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
287
284
  rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
@@ -353,6 +350,10 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
353
350
  # @comment
354
351
  # rubocop:enable MethodLength
355
352
 
353
+ def visit_keyframerule(node)
354
+ visit_directive(node)
355
+ end
356
+
356
357
  private
357
358
 
358
359
  def debug_info_rule(debug_info, options)
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'erb'
2
3
  require 'set'
3
4
  require 'enumerator'
@@ -337,6 +338,18 @@ module Sass
337
338
  minuend.select {|e| set.include?(e)}
338
339
  end
339
340
 
341
+ # Returns the maximum of `val1` and `val2`. We use this over \{Array.max} to
342
+ # avoid unnecessary garbage collection.
343
+ def max(val1, val2)
344
+ val1 > val2 ? val1 : val2
345
+ end
346
+
347
+ # Returns the minimum of `val1` and `val2`. We use this over \{Array.min} to
348
+ # avoid unnecessary garbage collection.
349
+ def min(val1, val2)
350
+ val1 <= val2 ? val1 : val2
351
+ end
352
+
340
353
  # Returns a string description of the character that caused an
341
354
  # `Encoding::UndefinedConversionError`.
342
355
  #
@@ -707,6 +720,14 @@ module Sass
707
720
  @ruby1_8_6 = ruby1_8? && RUBY_VERSION_COMPONENTS[2] < 7
708
721
  end
709
722
 
723
+ # Whether or not this is running under Ruby 1.9.2 exactly.
724
+ #
725
+ # @return [Boolean]
726
+ def ruby1_9_2?
727
+ return @ruby1_9_2 if defined?(@ruby1_9_2)
728
+ @ruby1_9_2 = RUBY_VERSION_COMPONENTS == [1, 9, 2]
729
+ end
730
+
710
731
  # Wehter or not this is running under JRuby 1.6 or lower.
711
732
  def jruby1_6?
712
733
  return @jruby1_6 if defined?(@jruby1_6)
@@ -751,114 +772,76 @@ module Sass
751
772
  (pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*flatten(pairs_or_hash, 1)]
752
773
  end
753
774
 
754
- # Checks that the encoding of a string is valid in Ruby 1.9
755
- # and cleans up potential encoding gotchas like the UTF-8 BOM.
756
- # If it's not, yields an error string describing the invalid character
757
- # and the line on which it occurrs.
758
- #
759
- # @param str [String] The string of which to check the encoding
760
- # @yield [msg] A block in which an encoding error can be raised.
761
- # Only yields if there is an encoding error
762
- # @yieldparam msg [String] The error message to be raised
763
- # @return [String] `str`, potentially with encoding gotchas like BOMs removed
764
- def check_encoding(str)
765
- if ruby1_8?
766
- return str.gsub(/\A\xEF\xBB\xBF/, '') # Get rid of the UTF-8 BOM
767
- elsif str.valid_encoding?
768
- # Get rid of the Unicode BOM if possible
769
- if str.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?$/
770
- return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
771
- else
772
- return str
773
- end
774
- end
775
-
776
- encoding = str.encoding
777
- newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
778
- str.force_encoding("binary").split(newlines).each_with_index do |line, i|
779
- begin
780
- line.encode(encoding)
781
- rescue Encoding::UndefinedConversionError => e
782
- yield <<MSG.rstrip, i + 1
783
- Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}
784
- MSG
785
- end
786
- end
787
- str
775
+ unless ruby1_8?
776
+ CHARSET_REGEXP = /\A@charset "([^"]+)"/
777
+ UTF_8_BOM = "\xEF\xBB\xBF".force_encoding('BINARY')
778
+ UTF_16BE_BOM = "\xFE\xFF".force_encoding('BINARY')
779
+ UTF_16LE_BOM = "\xFF\xFE".force_encoding('BINARY')
788
780
  end
789
781
 
790
782
  # Like {\#check\_encoding}, but also checks for a `@charset` declaration
791
783
  # at the beginning of the file and uses that encoding if it exists.
792
784
  #
793
- # The Sass encoding rules are simple.
794
- # If a `@charset` declaration exists,
795
- # we assume that that's the original encoding of the document.
796
- # Otherwise, we use whatever encoding Ruby has.
797
- # Then we convert that to UTF-8 to process internally.
798
- # The UTF-8 end result is what's returned by this method.
785
+ # Sass follows CSS's decoding rules.
799
786
  #
800
787
  # @param str [String] The string of which to check the encoding
801
- # @yield [msg] A block in which an encoding error can be raised.
802
- # Only yields if there is an encoding error
803
- # @yieldparam msg [String] The error message to be raised
804
788
  # @return [(String, Encoding)] The original string encoded as UTF-8,
805
789
  # and the source encoding of the string (or `nil` under Ruby 1.8)
806
790
  # @raise [Encoding::UndefinedConversionError] if the source encoding
807
791
  # cannot be converted to UTF-8
808
792
  # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
809
- def check_sass_encoding(str, &block)
810
- return check_encoding(str, &block), nil if ruby1_8?
811
- # We allow any printable ASCII characters but double quotes in the charset decl
812
- bin = str.dup.force_encoding("BINARY")
813
- encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
814
- re = Sass::Util::CHARSET_REGEXPS[enc]
815
- re && bin =~ re
793
+ # @raise [Sass::SyntaxError] If the document declares an encoding that
794
+ # doesn't match its contents, or it doesn't declare an encoding and its
795
+ # contents are invalid in the native encoding.
796
+ def check_sass_encoding(str)
797
+ # On Ruby 1.8 we can't do anything complicated with encodings.
798
+ # Instead, we just strip out a UTF-8 BOM if it exists and
799
+ # sanitize according to Section 3.3 of CSS Syntax Level 3. We
800
+ # don't sanitize null characters since they might be components
801
+ # of other characters.
802
+ if ruby1_8?
803
+ return str.gsub(/\A\xEF\xBB\xBF/, '').gsub(/\r\n?|\f/, "\n"), nil
816
804
  end
817
- charset, bom = $1, $2
818
- if charset
819
- charset = charset.force_encoding(encoding).encode("UTF-8")
820
- if (endianness = encoding[/[BL]E$/])
821
- begin
822
- Encoding.find(charset + endianness)
823
- charset << endianness
824
- rescue ArgumentError # Encoding charset + endianness doesn't exist
805
+
806
+ # Determine the fallback encoding following section 3.2 of CSS Syntax Level 3 and Encodings:
807
+ # http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#determine-the-fallback-encoding
808
+ # http://encoding.spec.whatwg.org/#decode
809
+ binary = str.dup.force_encoding("BINARY")
810
+ if binary.start_with?(UTF_8_BOM)
811
+ binary.slice! 0, UTF_8_BOM.length
812
+ str = binary.force_encoding('UTF-8')
813
+ elsif binary.start_with?(UTF_16BE_BOM)
814
+ binary.slice! 0, UTF_16BE_BOM.length
815
+ str = binary.force_encoding('UTF-16BE')
816
+ elsif binary.start_with?(UTF_16LE_BOM)
817
+ binary.slice! 0, UTF_16LE_BOM.length
818
+ str = binary.force_encoding('UTF-16LE')
819
+ elsif binary =~ CHARSET_REGEXP
820
+ charset = $1.force_encoding('US-ASCII')
821
+ # Ruby 1.9.2 doesn't recognize a UTF-16 encoding without an endian marker.
822
+ if ruby1_9_2? && charset.downcase == 'utf-16'
823
+ encoding = Encoding.find('UTF-8')
824
+ else
825
+ encoding = Encoding.find(charset)
826
+ if encoding.name == 'UTF-16' || encoding.name == 'UTF-16BE'
827
+ encoding = Encoding.find('UTF-8')
825
828
  end
826
829
  end
827
- str.force_encoding(charset)
828
- elsif bom
829
- str.force_encoding(encoding)
830
+ str = binary.force_encoding(encoding)
831
+ elsif str.encoding.name == "ASCII-8BIT"
832
+ # Normally we want to fall back on believing the Ruby string
833
+ # encoding, but if that's just binary we want to make sure
834
+ # it's valid UTF-8.
835
+ str = str.force_encoding('utf-8')
830
836
  end
831
837
 
832
- str = check_encoding(str, &block)
833
- return str.encode("UTF-8"), str.encoding
834
- end
838
+ find_encoding_error(str) unless str.valid_encoding?
835
839
 
836
- unless ruby1_8?
837
- # @private
838
- def _enc(string, encoding)
839
- string.encode(encoding).force_encoding("BINARY")
840
- end
841
-
842
- # We could automatically add in any non-ASCII-compatible encodings here,
843
- # but there's not really a good way to do that
844
- # without manually checking that each encoding
845
- # encodes all ASCII characters properly,
846
- # which takes long enough to affect the startup time of the CLI.
847
- ENCODINGS_TO_CHECK = %w[UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE]
848
-
849
- CHARSET_REGEXPS = Hash.new do |h, e|
850
- h[e] =
851
- begin
852
- # /\A(?:\uFEFF)?@charset "(.*?)"|\A(\uFEFF)/
853
- Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
854
- _enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
855
- _enc("\uFEFF", e)})/)
856
- rescue Encoding::ConverterNotFoundError => _
857
- nil # JRuby on Java 5 doesn't support UTF-32
858
- rescue
859
- # /\A@charset "(.*?)"/
860
- Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
861
- end
840
+ begin
841
+ # If the string is valid, preprocess it according to section 3.3 of CSS Syntax Level 3.
842
+ return str.encode("UTF-8").gsub(/\r\n?|\f/, "\n").tr("\u0000", "�"), str.encoding
843
+ rescue EncodingError
844
+ find_encoding_error(str)
862
845
  end
863
846
  end
864
847
 
@@ -1268,6 +1251,27 @@ MSG
1268
1251
 
1269
1252
  private
1270
1253
 
1254
+ def find_encoding_error(str)
1255
+ encoding = str.encoding
1256
+ cr = Regexp.quote("\r".encode(encoding).force_encoding('BINARY'))
1257
+ lf = Regexp.quote("\n".encode(encoding).force_encoding('BINARY'))
1258
+ ff = Regexp.quote("\f".encode(encoding).force_encoding('BINARY'))
1259
+ line_break = /#{cr}#{lf}?|#{ff}|#{lf}/
1260
+
1261
+ str.force_encoding("binary").split(line_break).each_with_index do |line, i|
1262
+ begin
1263
+ line.encode(encoding)
1264
+ rescue Encoding::UndefinedConversionError => e
1265
+ raise Sass::SyntaxError.new(
1266
+ "Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}",
1267
+ :line => i + 1)
1268
+ end
1269
+ end
1270
+
1271
+ # We shouldn't get here, but it's possible some weird encoding stuff causes it.
1272
+ return str, str.encoding
1273
+ end
1274
+
1271
1275
  # rubocop:disable LineLength
1272
1276
 
1273
1277
  # Calculates the memoization table for the Least Common Subsequence algorithm.