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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +5 -5
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/VERSION_NAME +1 -1
- data/bin/sass +1 -1
- data/bin/scss +1 -1
- data/lib/sass.rb +0 -5
- data/lib/sass/css.rb +1 -3
- data/lib/sass/engine.rb +28 -39
- data/lib/sass/environment.rb +13 -17
- data/lib/sass/error.rb +6 -9
- data/lib/sass/exec.rb +5 -771
- data/lib/sass/exec/base.rb +187 -0
- data/lib/sass/exec/sass_convert.rb +264 -0
- data/lib/sass/exec/sass_scss.rb +419 -0
- data/lib/sass/features.rb +6 -0
- data/lib/sass/importers.rb +0 -1
- data/lib/sass/importers/base.rb +5 -1
- data/lib/sass/importers/filesystem.rb +4 -21
- data/lib/sass/media.rb +1 -4
- data/lib/sass/plugin/compiler.rb +32 -136
- data/lib/sass/script/css_lexer.rb +1 -1
- data/lib/sass/script/functions.rb +363 -39
- data/lib/sass/script/lexer.rb +68 -50
- data/lib/sass/script/parser.rb +29 -14
- data/lib/sass/script/tree.rb +1 -0
- data/lib/sass/script/tree/funcall.rb +1 -1
- data/lib/sass/script/tree/interpolation.rb +19 -1
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/value.rb +0 -1
- data/lib/sass/script/value/bool.rb +0 -5
- data/lib/sass/script/value/color.rb +32 -12
- data/lib/sass/script/value/helpers.rb +107 -0
- data/lib/sass/script/value/list.rb +0 -15
- data/lib/sass/script/value/null.rb +0 -5
- data/lib/sass/script/value/number.rb +60 -14
- data/lib/sass/script/value/string.rb +53 -9
- data/lib/sass/scss/css_parser.rb +8 -2
- data/lib/sass/scss/parser.rb +175 -319
- data/lib/sass/scss/rx.rb +14 -5
- data/lib/sass/scss/static_parser.rb +298 -1
- data/lib/sass/selector.rb +56 -193
- data/lib/sass/selector/abstract_sequence.rb +28 -13
- data/lib/sass/selector/comma_sequence.rb +91 -12
- data/lib/sass/selector/pseudo.rb +256 -0
- data/lib/sass/selector/sequence.rb +99 -31
- data/lib/sass/selector/simple.rb +14 -25
- data/lib/sass/selector/simple_sequence.rb +101 -37
- data/lib/sass/shared.rb +1 -1
- data/lib/sass/source/map.rb +23 -9
- data/lib/sass/stack.rb +0 -6
- data/lib/sass/supports.rb +1 -1
- data/lib/sass/tree/at_root_node.rb +1 -0
- data/lib/sass/tree/directive_node.rb +7 -1
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/prop_node.rb +1 -1
- data/lib/sass/tree/rule_node.rb +11 -6
- data/lib/sass/tree/visitors/check_nesting.rb +3 -4
- data/lib/sass/tree/visitors/convert.rb +8 -17
- data/lib/sass/tree/visitors/cssize.rb +12 -24
- data/lib/sass/tree/visitors/deep_copy.rb +5 -0
- data/lib/sass/tree/visitors/perform.rb +43 -28
- data/lib/sass/tree/visitors/set_options.rb +5 -0
- data/lib/sass/tree/visitors/to_css.rb +14 -13
- data/lib/sass/util.rb +94 -90
- data/test/sass/cache_test.rb +1 -1
- data/test/sass/callbacks_test.rb +1 -1
- data/test/sass/compiler_test.rb +5 -14
- data/test/sass/conversion_test.rb +47 -1
- data/test/sass/css2sass_test.rb +3 -3
- data/test/sass/encoding_test.rb +219 -0
- data/test/sass/engine_test.rb +128 -191
- data/test/sass/exec_test.rb +2 -2
- data/test/sass/extend_test.rb +234 -17
- data/test/sass/functions_test.rb +268 -213
- data/test/sass/importer_test.rb +31 -21
- data/test/sass/logger_test.rb +1 -1
- data/test/sass/more_results/more_import.css +1 -1
- data/test/sass/plugin_test.rb +12 -11
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +4 -4
- data/test/sass/results/expanded.css +1 -1
- data/test/sass/results/import.css +1 -1
- data/test/sass/results/import_charset_ibm866.css +2 -2
- data/test/sass/results/mixins.css +17 -17
- data/test/sass/results/nested.css +1 -1
- data/test/sass/results/parent_ref.css +2 -2
- data/test/sass/results/script.css +3 -3
- data/test/sass/results/scss_import.css +1 -1
- data/test/sass/script_conversion_test.rb +7 -4
- data/test/sass/script_test.rb +202 -79
- data/test/sass/scss/css_test.rb +95 -25
- data/test/sass/scss/rx_test.rb +4 -4
- data/test/sass/scss/scss_test.rb +363 -19
- data/test/sass/source_map_test.rb +48 -41
- data/test/sass/superselector_test.rb +191 -0
- data/test/sass/templates/scss_import.scss +2 -1
- data/test/sass/test_helper.rb +1 -1
- data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
- data/test/sass/util/normalized_map_test.rb +1 -1
- data/test/sass/util/subset_map_test.rb +2 -2
- data/test/sass/util_test.rb +1 -1
- data/test/sass/value_helpers_test.rb +3 -3
- data/test/test_helper.rb +2 -2
- metadata +30 -7
- data/lib/sass/importers/deprecated_path.rb +0 -51
- 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
|
data/lib/sass/tree/prop_node.rb
CHANGED
@@ -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 :
|
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")
|
data/lib/sass/tree/rule_node.rb
CHANGED
@@ -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
|
-
|
68
|
-
|
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,
|
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.
|
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
|
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
|
-
|
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
|
-
|
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
|
127
|
-
|
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
|
-
|
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
|
@@ -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
|
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
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
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
|
-
|
418
|
-
if node.
|
419
|
-
|
420
|
-
|
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)
|
@@ -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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
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.
|
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)
|
data/lib/sass/util.rb
CHANGED
@@ -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
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
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
|
-
#
|
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
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
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
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
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(
|
828
|
-
elsif
|
829
|
-
|
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
|
833
|
-
return str.encode("UTF-8"), str.encoding
|
834
|
-
end
|
838
|
+
find_encoding_error(str) unless str.valid_encoding?
|
835
839
|
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
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.
|