sass 3.4.25 → 3.5.0

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +2 -117
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/lib/sass/cache_stores/filesystem.rb +1 -1
  8. data/lib/sass/css.rb +1 -2
  9. data/lib/sass/engine.rb +38 -20
  10. data/lib/sass/environment.rb +26 -5
  11. data/lib/sass/error.rb +2 -2
  12. data/lib/sass/exec/base.rb +2 -11
  13. data/lib/sass/features.rb +1 -0
  14. data/lib/sass/importers/filesystem.rb +2 -2
  15. data/lib/sass/plugin/compiler.rb +20 -50
  16. data/lib/sass/plugin/configuration.rb +2 -2
  17. data/lib/sass/railtie.rb +1 -1
  18. data/lib/sass/script/functions.rb +235 -43
  19. data/lib/sass/script/lexer.rb +13 -2
  20. data/lib/sass/script/parser.rb +69 -67
  21. data/lib/sass/script/tree/funcall.rb +35 -30
  22. data/lib/sass/script/tree/list_literal.rb +23 -8
  23. data/lib/sass/script/tree/map_literal.rb +2 -2
  24. data/lib/sass/script/tree/node.rb +0 -8
  25. data/lib/sass/script/tree/operation.rb +0 -7
  26. data/lib/sass/script/value/arg_list.rb +1 -1
  27. data/lib/sass/script/value/base.rb +17 -0
  28. data/lib/sass/script/value/callable.rb +25 -0
  29. data/lib/sass/script/value/color.rb +5 -0
  30. data/lib/sass/script/value/function.rb +19 -0
  31. data/lib/sass/script/value/helpers.rb +29 -9
  32. data/lib/sass/script/value/list.rb +35 -14
  33. data/lib/sass/script/value/map.rb +2 -2
  34. data/lib/sass/script/value/number.rb +1 -1
  35. data/lib/sass/script/value.rb +2 -0
  36. data/lib/sass/scss/parser.rb +99 -40
  37. data/lib/sass/scss/rx.rb +4 -10
  38. data/lib/sass/scss/static_parser.rb +5 -24
  39. data/lib/sass/selector/abstract_sequence.rb +7 -6
  40. data/lib/sass/selector/comma_sequence.rb +7 -4
  41. data/lib/sass/selector/pseudo.rb +19 -2
  42. data/lib/sass/selector/sequence.rb +35 -6
  43. data/lib/sass/selector/simple.rb +9 -2
  44. data/lib/sass/selector/simple_sequence.rb +8 -4
  45. data/lib/sass/selector.rb +4 -0
  46. data/lib/sass/source/map.rb +2 -2
  47. data/lib/sass/stack.rb +21 -1
  48. data/lib/sass/tree/charset_node.rb +1 -1
  49. data/lib/sass/tree/prop_node.rb +45 -53
  50. data/lib/sass/tree/rule_node.rb +3 -2
  51. data/lib/sass/tree/visitors/check_nesting.rb +1 -1
  52. data/lib/sass/tree/visitors/convert.rb +2 -3
  53. data/lib/sass/tree/visitors/cssize.rb +4 -15
  54. data/lib/sass/tree/visitors/deep_copy.rb +1 -1
  55. data/lib/sass/tree/visitors/extend.rb +2 -8
  56. data/lib/sass/tree/visitors/perform.rb +21 -9
  57. data/lib/sass/tree/visitors/set_options.rb +1 -1
  58. data/lib/sass/tree/visitors/to_css.rb +47 -13
  59. data/lib/sass/util/multibyte_string_scanner.rb +127 -131
  60. data/lib/sass/util/normalized_map.rb +1 -8
  61. data/lib/sass/util.rb +12 -290
  62. data/lib/sass/version.rb +0 -2
  63. data/lib/sass.rb +0 -7
  64. data/test/sass/cache_test.rb +0 -1
  65. data/test/sass/callbacks_test.rb +0 -1
  66. data/test/sass/compiler_test.rb +4 -15
  67. data/test/sass/conversion_test.rb +113 -163
  68. data/test/sass/css2sass_test.rb +16 -19
  69. data/test/sass/css_variable_test.rb +176 -71
  70. data/test/sass/encoding_test.rb +2 -33
  71. data/test/sass/engine_test.rb +71 -19
  72. data/test/sass/exec_test.rb +0 -1
  73. data/test/sass/extend_test.rb +37 -92
  74. data/test/sass/functions_test.rb +58 -14
  75. data/test/sass/importer_test.rb +2 -3
  76. data/test/sass/logger_test.rb +0 -1
  77. data/test/sass/plugin_test.rb +6 -10
  78. data/test/sass/results/script.css +1 -1
  79. data/test/sass/results/units.css +2 -2
  80. data/test/sass/script_conversion_test.rb +10 -2
  81. data/test/sass/script_test.rb +22 -24
  82. data/test/sass/scss/css_test.rb +3 -18
  83. data/test/sass/scss/rx_test.rb +0 -1
  84. data/test/sass/scss/scss_test.rb +49 -16
  85. data/test/sass/source_map_test.rb +13 -16
  86. data/test/sass/superselector_test.rb +0 -1
  87. data/test/sass/util/multibyte_string_scanner_test.rb +121 -124
  88. data/test/sass/util/normalized_map_test.rb +0 -1
  89. data/test/sass/util/subset_map_test.rb +0 -1
  90. data/test/sass/util_test.rb +4 -39
  91. data/test/sass/value_helpers_test.rb +0 -1
  92. data/test/sass-spec.yml +1 -1
  93. data/test/test_helper.rb +39 -0
  94. metadata +19 -44
  95. data/lib/sass/script/css_variable_warning.rb +0 -52
  96. data/lib/sass/util/cross_platform_random.rb +0 -19
  97. data/lib/sass/util/ordered_hash.rb +0 -192
  98. data/test/sass/results/import_charset_1_8.css +0 -5
  99. data/test/sass/templates/import_charset_1_8.sass +0 -6
  100. data/vendor/listen/CHANGELOG.md +0 -1
  101. data/vendor/listen/CONTRIBUTING.md +0 -38
  102. data/vendor/listen/Gemfile +0 -20
  103. data/vendor/listen/Guardfile +0 -8
  104. data/vendor/listen/LICENSE +0 -20
  105. data/vendor/listen/README.md +0 -349
  106. data/vendor/listen/Rakefile +0 -5
  107. data/vendor/listen/Vagrantfile +0 -96
  108. data/vendor/listen/lib/listen/adapter.rb +0 -327
  109. data/vendor/listen/lib/listen/adapters/bsd.rb +0 -75
  110. data/vendor/listen/lib/listen/adapters/darwin.rb +0 -48
  111. data/vendor/listen/lib/listen/adapters/linux.rb +0 -81
  112. data/vendor/listen/lib/listen/adapters/polling.rb +0 -58
  113. data/vendor/listen/lib/listen/adapters/windows.rb +0 -91
  114. data/vendor/listen/lib/listen/directory_record.rb +0 -406
  115. data/vendor/listen/lib/listen/listener.rb +0 -323
  116. data/vendor/listen/lib/listen/turnstile.rb +0 -32
  117. data/vendor/listen/lib/listen/version.rb +0 -3
  118. data/vendor/listen/lib/listen.rb +0 -54
  119. data/vendor/listen/listen.gemspec +0 -28
  120. data/vendor/listen/spec/listen/adapter_spec.rb +0 -149
  121. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
  122. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
  123. data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
  124. data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
  125. data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
  126. data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1250
  127. data/vendor/listen/spec/listen/listener_spec.rb +0 -258
  128. data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
  129. data/vendor/listen/spec/listen_spec.rb +0 -67
  130. data/vendor/listen/spec/spec_helper.rb +0 -25
  131. data/vendor/listen/spec/support/adapter_helper.rb +0 -666
  132. data/vendor/listen/spec/support/directory_record_helper.rb +0 -57
  133. data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
  134. data/vendor/listen/spec/support/listeners_helper.rb +0 -179
  135. data/vendor/listen/spec/support/platform_helper.rb +0 -15
@@ -66,10 +66,14 @@ module Sass
66
66
  next [sseq_or_op] unless sseq_or_op.is_a?(SimpleSequence)
67
67
  sseq_or_op.resolve_parent_refs(super_cseq).members
68
68
  end).map do |path|
69
- Sequence.new(path.map do |seq_or_op|
69
+ path_members = path.map do |seq_or_op|
70
70
  next seq_or_op unless seq_or_op.is_a?(Sequence)
71
71
  seq_or_op.members
72
- end.flatten)
72
+ end
73
+ if path_members.length == 2 && path_members[1][0] == "\n"
74
+ path_members[0].unshift path_members[1].shift
75
+ end
76
+ Sequence.new(path_members.flatten)
73
77
  end)
74
78
  end
75
79
 
@@ -115,7 +119,7 @@ module Sass
115
119
  # specificity greater than or equal to that of the original selector.
116
120
  # In order to ensure that, we record the original selector's
117
121
  # (`extended.first`) original specificity.
118
- extended.first.add_sources!([self]) if original && !has_placeholder?
122
+ extended.first.add_sources!([self]) if original && !invisible?
119
123
 
120
124
  extended.map {|seq| seq.members}
121
125
  end
@@ -283,6 +287,9 @@ module Sass
283
287
  next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
284
288
  next s2 if parent_superselector?(s1, s2)
285
289
  next s1 if parent_superselector?(s2, s1)
290
+ next unless must_unify?(s1, s2)
291
+ next unless (unified = Sequence.new(s1).unify(Sequence.new(s2)))
292
+ unified.members.first.members if unified.members.length == 1
286
293
  end
287
294
 
288
295
  diff = [[init]]
@@ -494,7 +501,7 @@ module Sass
494
501
  return if seq1.size > seq2.size
495
502
  return seq1.first.superselector?(seq2.last, seq2[0...-1]) if seq1.size == 1
496
503
 
497
- _, si = Sass::Util.enum_with_index(seq2).find do |e, i|
504
+ _, si = seq2.each_with_index.find do |e, i|
498
505
  return if i == seq2.size - 1
499
506
  next if e.is_a?(String)
500
507
  seq1.first.superselector?(e, seq2[0...i])
@@ -537,6 +544,30 @@ module Sass
537
544
  _superselector?(seq1 + [base], seq2 + [base])
538
545
  end
539
546
 
547
+ # Returns whether two selectors must be unified to produce a valid
548
+ # combined selector. This is true when both selectors contain the same
549
+ # unique simple selector such as an id.
550
+ #
551
+ # @param seq1 [Array<SimpleSequence or String>]
552
+ # @param seq2 [Array<SimpleSequence or String>]
553
+ # @return [Boolean]
554
+ def must_unify?(seq1, seq2)
555
+ unique_selectors = seq1.map do |sseq|
556
+ next [] if sseq.is_a?(String)
557
+ sseq.members.select {|sel| sel.unique?}
558
+ end.flatten.to_set
559
+
560
+ return false if unique_selectors.empty?
561
+
562
+ seq2.any? do |sseq|
563
+ next false if sseq.is_a?(String)
564
+ sseq.members.any? do |sel|
565
+ next unless sel.unique?
566
+ unique_selectors.include?(sel)
567
+ end
568
+ end
569
+ end
570
+
540
571
  # Removes redundant selectors from between multiple lists of
541
572
  # selectors. This takes a list of lists of selector sequences;
542
573
  # each individual list is assumed to have no redundancy within
@@ -597,8 +628,6 @@ module Sass
597
628
  other.members.reject {|m| m == "\n"}.eql?(members.reject {|m| m == "\n"})
598
629
  end
599
630
 
600
- private
601
-
602
631
  def path_has_two_subjects?(path)
603
632
  subject = false
604
633
  path.each do |sseq_or_op|
@@ -14,6 +14,14 @@ module Sass
14
14
  # @return [String, nil]
15
15
  attr_accessor :filename
16
16
 
17
+ # Whether only one instance of this simple selector is allowed in a given
18
+ # complex selector.
19
+ #
20
+ # @return [Boolean]
21
+ def unique?
22
+ false
23
+ end
24
+
17
25
  # @see #to_s
18
26
  #
19
27
  # @return [String]
@@ -74,9 +82,8 @@ module Sass
74
82
  def unify(sels)
75
83
  return sels.first.unify([self]) if sels.length == 1 && sels.first.is_a?(Universal)
76
84
  return sels if sels.any? {|sel2| eql?(sel2)}
77
- sels_with_ix = Sass::Util.enum_with_index(sels)
78
85
  if !is_a?(Pseudo) || (sels.last.is_a?(Pseudo) && sels.last.type == :element)
79
- _, i = sels_with_ix.find {|sel, _| sel.is_a?(Pseudo)}
86
+ _, i = sels.each_with_index.find {|sel, _| sel.is_a?(Pseudo)}
80
87
  end
81
88
  return sels + [self] unless i
82
89
  sels[0...i] + [self] + sels[i..-1]
@@ -163,7 +163,7 @@ module Sass
163
163
  next sel if seen.include?([sel])
164
164
  extended = sel.selector.do_extend(extends, parent_directives, replace, seen, false)
165
165
  next sel if extended == sel.selector
166
- extended.members.reject! {|seq| seq.has_placeholder?}
166
+ extended.members.reject! {|seq| seq.invisible?}
167
167
 
168
168
  # For `:not()`, we usually want to get rid of any complex
169
169
  # selectors because that will cause the selector to fail to
@@ -184,17 +184,16 @@ module Sass
184
184
  result
185
185
  end.flatten
186
186
 
187
- groups = Sass::Util.group_by_to_a(extends[members.to_set]) {|ex| ex.extender}
187
+ groups = extends[members.to_set].group_by {|ex| ex.extender}.to_a
188
188
  groups.map! do |seq, group|
189
189
  sels = group.map {|e| e.target}.flatten
190
190
  # If A {@extend B} and C {...},
191
191
  # seq is A, sels is B, and self is C
192
192
 
193
193
  self_without_sel = Sass::Util.array_minus(members, sels)
194
- group.each {|e| e.result = :failed_to_unify unless e.result == :succeeded}
194
+ group.each {|e| e.success = true}
195
195
  unified = seq.members.last.unify(SimpleSequence.new(self_without_sel, subject?))
196
196
  next unless unified
197
- group.each {|e| e.result = :succeeded}
198
197
  group.each {|e| check_directives_match!(e, parent_directives)}
199
198
  new_seq = Sequence.new(seq.members[0...-1] + [unified])
200
199
  new_seq.add_sources!(sources + [seq])
@@ -287,6 +286,11 @@ module Sass
287
286
  # @see Simple#to_s
288
287
  def to_s(opts = {})
289
288
  res = @members.map {|m| m.to_s(opts)}.join
289
+
290
+ # :not(%foo) may resolve to the empty string, but it should match every
291
+ # selector so we replace it with "*".
292
+ res = '*' if res.empty?
293
+
290
294
  res << '!' if subject?
291
295
  res
292
296
  end
data/lib/sass/selector.rb CHANGED
@@ -87,6 +87,10 @@ module Sass
87
87
  @name = name
88
88
  end
89
89
 
90
+ def unique?
91
+ true
92
+ end
93
+
90
94
  # @see Selector#to_s
91
95
  def to_s(opts = {})
92
96
  "#" + @name
@@ -93,8 +93,8 @@ module Sass::Source
93
93
  raise ArgumentError.new("Sass::Source::Map#to_json requires either " \
94
94
  "the :css_uri option or both the :css_path and :soucemap_path options.")
95
95
  end
96
- css_path &&= Sass::Util.pathname(Sass::Util.absolute_path(css_path))
97
- sourcemap_path &&= Sass::Util.pathname(Sass::Util.absolute_path(sourcemap_path))
96
+ css_path &&= Sass::Util.pathname(File.absolute_path(css_path))
97
+ sourcemap_path &&= Sass::Util.pathname(File.absolute_path(sourcemap_path))
98
98
  css_uri ||= Sass::Util.file_uri_from_path(
99
99
  Sass::Util.relative_path_from(css_path, sourcemap_path.dirname))
100
100
 
data/lib/sass/stack.rb CHANGED
@@ -98,8 +98,28 @@ module Sass
98
98
  with_frame(filename, line, :mixin, name) {yield}
99
99
  end
100
100
 
101
+ # Pushes a function frame onto the stack.
102
+ #
103
+ # @param filename [String] See \{Frame#filename}.
104
+ # @param line [String] See \{Frame#line}.
105
+ # @param name [String] See \{Frame#name}.
106
+ # @yield [] A block in which the new frame is on the stack.
107
+ def with_function(filename, line, name)
108
+ with_frame(filename, line, :function, name) {yield}
109
+ end
110
+
111
+ # Pushes a function frame onto the stack.
112
+ #
113
+ # @param filename [String] See \{Frame#filename}.
114
+ # @param line [String] See \{Frame#line}.
115
+ # @param name [String] See \{Frame#name}.
116
+ # @yield [] A block in which the new frame is on the stack.
117
+ def with_directive(filename, line, name)
118
+ with_frame(filename, line, :directive, name) {yield}
119
+ end
120
+
101
121
  def to_s
102
- Sass::Util.enum_with_index(Sass::Util.enum_cons(frames.reverse + [nil], 2)).
122
+ (frames.reverse + [nil]).each_cons(2).each_with_index.
103
123
  map do |(frame, caller), i|
104
124
  "#{i == 0 ? 'on' : 'from'} line #{frame.line}" +
105
125
  " of #{frame.filename || 'an unknown file'}" +
@@ -16,7 +16,7 @@ module Sass::Tree
16
16
 
17
17
  # @see Node#invisible?
18
18
  def invisible?
19
- !Sass::Util.ruby1_8?
19
+ true
20
20
  end
21
21
  end
22
22
  end
@@ -20,7 +20,11 @@ module Sass::Tree
20
20
 
21
21
  # The value of the property.
22
22
  #
23
- # @return [Sass::Script::Tree::Node]
23
+ # For most properties, this will just contain a single Node. However, for
24
+ # CSS variables, it will contain multiple strings and nodes representing
25
+ # interpolation. Any adjacent strings will be merged together.
26
+ #
27
+ # @return [Array<String, Sass::Script::Tree::Node>]
24
28
  attr_accessor :value
25
29
 
26
30
  # The value of the property
@@ -52,14 +56,22 @@ module Sass::Tree
52
56
  # @return [Sass::Source::Range]
53
57
  attr_accessor :value_source_range
54
58
 
59
+ # Whether this represents a CSS custom property.
60
+ #
61
+ # @return [Boolean]
62
+ def custom_property?
63
+ name.first.is_a?(String) && name.first.start_with?("--")
64
+ end
65
+
55
66
  # @param name [Array<String, Sass::Script::Tree::Node>] See \{#name}
56
- # @param value [Sass::Script::Tree::Node] See \{#value}
67
+ # @param value [Array<String, Sass::Script::Tree::Node>] See \{#value}
57
68
  # @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
58
69
  # `:old` if it uses `:a b`-style syntax
59
70
  def initialize(name, value, prop_syntax)
60
71
  @name = Sass::Util.strip_string_array(
61
72
  Sass::Util.merge_adjacent_strings(name))
62
- @value = value
73
+ @value = Sass::Util.merge_adjacent_strings(value)
74
+ @value = Sass::Util.strip_string_array(@value) unless custom_property?
63
75
  @tabs = 0
64
76
  @prop_syntax = prop_syntax
65
77
  super()
@@ -81,9 +93,10 @@ module Sass::Tree
81
93
  # @return [String] The message
82
94
  def pseudo_class_selector_message
83
95
  if @prop_syntax == :new ||
84
- !value.is_a?(Sass::Script::Tree::Literal) ||
85
- !value.value.is_a?(Sass::Script::Value::String) ||
86
- !value.value.value.empty?
96
+ custom_property? ||
97
+ !value.first.is_a?(Sass::Script::Tree::Literal) ||
98
+ !value.first.value.is_a?(Sass::Script::Value::String) ||
99
+ !value.first.value.value.empty?
87
100
  return ""
88
101
  end
89
102
 
@@ -98,73 +111,52 @@ module Sass::Tree
98
111
  # @param fmt [Symbol] `:scss` or `:sass`.
99
112
  def declaration(opts = {:old => @prop_syntax == :old}, fmt = :sass)
100
113
  name = self.name.map {|n| n.is_a?(String) ? n : n.to_sass(opts)}.join
114
+ value = self.value.map {|n| n.is_a?(String) ? n : n.to_sass(opts)}.join
115
+ value = "(#{value})" if value_needs_parens?
116
+
101
117
  if name[0] == ?:
102
- raise Sass::SyntaxError.new("The \"#{name}: #{self.class.val_to_sass(value, opts)}\"" +
118
+ raise Sass::SyntaxError.new("The \"#{name}: #{value}\"" +
103
119
  " hack is not allowed in the Sass indented syntax")
104
120
  end
105
121
 
122
+ # The indented syntax doesn't support newlines in custom property values,
123
+ # but we can losslessly convert them to spaces instead.
124
+ value = value.tr("\n", " ") if fmt == :sass
125
+
106
126
  old = opts[:old] && fmt == :sass
107
- initial = old ? ':' : ''
108
- mid = old ? '' : ':'
109
- "#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip
127
+ "#{old ? ':' : ''}#{name}#{old ? '' : ':'}#{custom_property? ? '' : ' '}#{value}".rstrip
110
128
  end
111
129
 
112
130
  # A property node is invisible if its value is empty.
113
131
  #
114
132
  # @return [Boolean]
115
133
  def invisible?
116
- resolved_value.empty?
134
+ !custom_property? && resolved_value.empty?
117
135
  end
118
136
 
119
137
  private
120
138
 
139
+ # Returns whether \{#value} neesd parentheses in order to be parsed
140
+ # properly as division.
141
+ def value_needs_parens?
142
+ return false if custom_property?
143
+
144
+ root = value.first
145
+ root.is_a?(Sass::Script::Tree::Operation) &&
146
+ root.operator == :div &&
147
+ root.operand1.is_a?(Sass::Script::Tree::Literal) &&
148
+ root.operand1.value.is_a?(Sass::Script::Value::Number) &&
149
+ root.operand1.value.original.nil? &&
150
+ root.operand2.is_a?(Sass::Script::Tree::Literal) &&
151
+ root.operand2.value.is_a?(Sass::Script::Value::Number) &&
152
+ root.operand2.value.original.nil?
153
+ end
154
+
121
155
  def check!
122
156
  return unless @options[:property_syntax] && @options[:property_syntax] != @prop_syntax
123
157
  raise Sass::SyntaxError.new(
124
158
  "Illegal property syntax: can't use #{@prop_syntax} syntax when " +
125
159
  ":property_syntax => #{@options[:property_syntax].inspect} is set.")
126
160
  end
127
-
128
- class << self
129
- # @private
130
- def val_to_sass(value, opts)
131
- val_to_sass_comma(value, opts).to_sass(opts)
132
- end
133
-
134
- private
135
-
136
- def val_to_sass_comma(node, opts)
137
- return node unless node.is_a?(Sass::Script::Tree::Operation)
138
- return val_to_sass_concat(node, opts) unless node.operator == :comma
139
-
140
- Sass::Script::Tree::Operation.new(
141
- val_to_sass_concat(node.operand1, opts),
142
- val_to_sass_comma(node.operand2, opts),
143
- node.operator)
144
- end
145
-
146
- def val_to_sass_concat(node, opts)
147
- return node unless node.is_a?(Sass::Script::Tree::Operation)
148
- return val_to_sass_div(node, opts) unless node.operator == :space
149
-
150
- Sass::Script::Tree::Operation.new(
151
- val_to_sass_div(node.operand1, opts),
152
- val_to_sass_concat(node.operand2, opts),
153
- node.operator)
154
- end
155
-
156
- def val_to_sass_div(node, opts)
157
- unless node.is_a?(Sass::Script::Tree::Operation) && node.operator == :div &&
158
- node.operand1.is_a?(Sass::Script::Tree::Literal) &&
159
- node.operand1.value.is_a?(Sass::Script::Value::Number) &&
160
- node.operand2.is_a?(Sass::Script::Tree::Literal) &&
161
- node.operand2.value.is_a?(Sass::Script::Value::Number) &&
162
- (!node.operand1.value.original || !node.operand2.value.original)
163
- return node
164
- end
165
-
166
- Sass::Script::Value::String.new("(#{node.to_sass(opts)})")
167
- end
168
- end
169
161
  end
170
162
  end
@@ -120,13 +120,14 @@ module Sass::Tree
120
120
  #
121
121
  # @return [{#to_s => #to_s}]
122
122
  def debug_info
123
- {:filename => filename && ("file://" + Sass::Util.escape_uri(File.expand_path(filename))),
123
+ {:filename => filename &&
124
+ ("file://" + URI::DEFAULT_PARSER.escape(File.expand_path(filename))),
124
125
  :line => line}
125
126
  end
126
127
 
127
128
  # A rule node is invisible if it has only placeholder selectors.
128
129
  def invisible?
129
- resolved_rules.members.all? {|seq| seq.has_placeholder?}
130
+ resolved_rules.members.all? {|seq| seq.invisible?}
130
131
  end
131
132
 
132
133
  private
@@ -31,7 +31,7 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
31
31
  if parent.is_a?(Sass::Tree::AtRootNode) && parent.resolved_value
32
32
  old_parents = @parents
33
33
  @parents = @parents.reject {|p| parent.exclude_node?(p)}
34
- @parent = Sass::Util.enum_with_index(@parents.reverse).
34
+ @parent = @parents.reverse.each_with_index.
35
35
  find {|p, i| !transparent_parent?(p, @parents[-i - 2])}.first
36
36
 
37
37
  begin
@@ -203,8 +203,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
203
203
 
204
204
  unless node.args.empty? && node.keywords.empty? && node.splat.nil?
205
205
  args = node.args.map(&arg_to_sass)
206
- keywords = Sass::Util.hash_to_a(node.keywords.as_stored).
207
- map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"}
206
+ keywords = node.keywords.as_stored.to_a.map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"}
208
207
 
209
208
  if node.splat
210
209
  splat = "#{arg_to_sass[node.splat]}..."
@@ -281,7 +280,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
281
280
  # Visit rule-level nodes and return their conversion with appropriate
282
281
  # whitespace added.
283
282
  def visit_rule_level(nodes)
284
- Sass::Util.enum_cons(nodes + [nil], 2).map do |child, nxt|
283
+ (nodes + [nil]).each_cons(2).map do |child, nxt|
285
284
  visit(child) +
286
285
  if nxt &&
287
286
  (child.is_a?(Sass::Tree::CommentNode) && child.line + child.lines + 1 == nxt.line) ||
@@ -56,8 +56,7 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
56
56
  @parents.pop
57
57
  end
58
58
 
59
- # In Ruby 1.8, ensures that there's only one `@charset` directive
60
- # and that it's at the top of the document.
59
+ # Converts the entire document to CSS.
61
60
  #
62
61
  # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
63
62
  # *and* the extensions defined for this tree
@@ -65,14 +64,6 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
65
64
  yield
66
65
 
67
66
  if parent.nil?
68
- # In Ruby 1.9 we can make all @charset nodes invisible
69
- # and infer the final @charset from the encoding of the final string.
70
- if Sass::Util.ruby1_8?
71
- charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
72
- node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
73
- node.children.unshift charset if charset
74
- end
75
-
76
67
  imports_to_move = []
77
68
  import_limit = nil
78
69
  i = -1
@@ -115,11 +106,9 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
115
106
  # @attr node [Sass::Tree::ExtendNode] The node that produced this extend.
116
107
  # @attr directives [Array<Sass::Tree::DirectiveNode>]
117
108
  # The directives containing the `@extend`.
118
- # @attr result [Symbol]
119
- # The result of this extend. One of `:not_found` (the target doesn't exist
120
- # in the document), `:failed_to_unify` (the target exists but cannot be
121
- # unified with the extender), or `:succeeded`.
122
- Extend = Struct.new(:extender, :target, :node, :directives, :result)
109
+ # @attr success [Boolean]
110
+ # Whether this extend successfully matched a selector.
111
+ Extend = Struct.new(:extender, :target, :node, :directives, :success)
123
112
 
124
113
  # Registers an extension in the `@extends` subset map.
125
114
  def visit_extend(node)
@@ -61,7 +61,7 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
61
61
 
62
62
  def visit_prop(node)
63
63
  node.name = node.name.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
64
- node.value = node.value.deep_copy
64
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
65
65
  yield
66
66
  end
67
67
 
@@ -49,19 +49,13 @@ class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
49
49
 
50
50
  def check_extends_fired!(extends)
51
51
  extends.each_value do |ex|
52
- next if ex.result == :succeeded || ex.node.optional?
52
+ next if ex.success || ex.node.optional?
53
53
  message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
54
- reason =
55
- if ex.result == :not_found
56
- "The selector \"#{ex.target.join}\" was not found."
57
- else
58
- "No selectors matching \"#{ex.target.join}\" could be unified with \"#{ex.extender}\"."
59
- end
60
54
 
61
55
  # TODO(nweiz): this should use the Sass stack trace of the extend node.
62
56
  raise Sass::SyntaxError.new(<<MESSAGE, :filename => ex.node.filename, :line => ex.node.line)
63
57
  #{message}
64
- #{reason}
58
+ The selector "#{ex.target.join}" was not found.
65
59
  Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
66
60
  MESSAGE
67
61
  end
@@ -290,7 +290,7 @@ WARNING
290
290
 
291
291
  @environment.set_local_function(node.name,
292
292
  Sass::Callable.new(node.name, node.args, node.splat, env,
293
- node.children, false, "function"))
293
+ node.children, false, "function", :stylesheet))
294
294
  []
295
295
  end
296
296
 
@@ -341,7 +341,7 @@ WARNING
341
341
  env = Sass::Environment.new(@environment, node.options)
342
342
  @environment.set_local_mixin(node.name,
343
343
  Sass::Callable.new(node.name, node.args, node.splat, env,
344
- node.children, node.has_content, "mixin"))
344
+ node.children, node.has_content, "mixin", :stylesheet))
345
345
  []
346
346
  end
347
347
 
@@ -395,9 +395,19 @@ WARNING
395
395
  # Runs any SassScript that may be embedded in a property.
396
396
  def visit_prop(node)
397
397
  node.resolved_name = run_interp(node.name)
398
- val = node.value.perform(@environment)
399
- node.resolved_value = val.to_s
400
- node.value_source_range = val.source_range if val.source_range
398
+
399
+ # If the node's value is just a variable or similar, we may get a useful
400
+ # source range from evaluating it.
401
+ if node.value.length == 1 && node.value.first.is_a?(Sass::Script::Tree::Node)
402
+ result = node.value.first.perform(@environment)
403
+ node.resolved_value = result.to_s
404
+ node.value_source_range = result.source_range if result.source_range
405
+ elsif node.custom_property?
406
+ node.resolved_value = run_interp_no_strip(node.value)
407
+ else
408
+ node.resolved_value = run_interp(node.value)
409
+ end
410
+
401
411
  yield
402
412
  end
403
413
 
@@ -483,9 +493,11 @@ WARNING
483
493
  def visit_warn(node)
484
494
  res = node.expr.perform(@environment)
485
495
  res = res.value if res.is_a?(Sass::Script::Value::String)
486
- msg = "WARNING: #{res}\n "
487
- msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
488
- Sass::Util.sass_warn msg
496
+ @environment.stack.with_directive(node.filename, node.line, "@warn") do
497
+ msg = "WARNING: #{res}\n "
498
+ msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
499
+ Sass::Util.sass_warn msg
500
+ end
489
501
  []
490
502
  end
491
503
 
@@ -556,7 +568,7 @@ WARNING
556
568
  end
557
569
 
558
570
  files << node.filename << node.imported_file.options[:filename]
559
- msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
571
+ msg << "\n" << files.each_cons(2).map do |m1, m2|
560
572
  " #{m1} imports #{m2}"
561
573
  end.join("\n")
562
574
  raise Sass::SyntaxError.new(msg)
@@ -88,7 +88,7 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
88
88
 
89
89
  def visit_prop(node)
90
90
  node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
91
- node.value.options = @options
91
+ node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
92
92
  yield
93
93
  end
94
94
 
@@ -135,7 +135,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
135
135
 
136
136
  output "\n"
137
137
 
138
- unless Sass::Util.ruby1_8? || @result.ascii_only?
138
+ unless @result.ascii_only?
139
139
  if node.style == :compressed
140
140
  # A byte order mark is sufficient to tell browsers that this
141
141
  # file is UTF-8 encoded, and will override any other detection
@@ -261,18 +261,20 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
261
261
  end
262
262
 
263
263
  def visit_prop(node)
264
- return if node.resolved_value.empty?
264
+ return if node.resolved_value.empty? && !node.custom_property?
265
265
  tab_str = ' ' * (@tabs + node.tabs)
266
266
  output(tab_str)
267
267
  for_node(node, :name) {output(node.resolved_name)}
268
- if node.style == :compressed
269
- output(":")
270
- for_node(node, :value) {output(node.resolved_value)}
271
- else
272
- output(": ")
273
- for_node(node, :value) {output(node.resolved_value)}
274
- output(";")
268
+ output(":")
269
+ output(" ") unless node.style == :compressed || node.custom_property?
270
+ for_node(node, :value) do
271
+ output(if node.custom_property?
272
+ format_custom_property_value(node)
273
+ else
274
+ node.resolved_value
275
+ end)
275
276
  end
277
+ output(";") unless node.style == :compressed
276
278
  end
277
279
 
278
280
  # @comment
@@ -294,8 +296,8 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
294
296
  end
295
297
 
296
298
  joined_rules = node.resolved_rules.members.map do |seq|
297
- next if seq.has_placeholder?
298
- rule_part = seq.to_s(:style => node.style)
299
+ next if seq.invisible?
300
+ rule_part = seq.to_s(style: node.style, placeholder: false)
299
301
  if node.style == :compressed
300
302
  rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
301
303
  rule_part.gsub!(/\s*([+>])\s*/m, '\1')
@@ -384,9 +386,41 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
384
386
 
385
387
  private
386
388
 
389
+ # Reformats the value of `node` so that it's nicely indented, preserving its
390
+ # existing relative indentation.
391
+ #
392
+ # @param node [Sass::Script::Tree::PropNode] A custom property node.
393
+ # @return [String]
394
+ def format_custom_property_value(node)
395
+ if node.style == :compact || node.style == :compressed || !node.resolved_value.include?("\n")
396
+ # Folding not involving newlines was done in the parser. We can safely
397
+ # fold newlines here because tokens like strings can't contain literal
398
+ # newlines, so we know any adjacent whitespace is tokenized as whitespace.
399
+ return node.resolved_value.gsub(/[ \t\r\f]*\n[ \t\r\f\n]*/, ' ')
400
+ end
401
+
402
+ # Find the smallest amount of indentation in the custom property and use
403
+ # that as the base indentation level.
404
+ lines = node.resolved_value.split("\n")
405
+ indented_lines = lines[1..-1]
406
+ min_indentation = indented_lines.
407
+ map {|line| line[/^[ \t]*/]}.
408
+ reject {|line| line.empty?}.
409
+ min_by {|line| line.length}
410
+
411
+ # Limit the base indentation to the same indentation level as the node name
412
+ # so that if *every* line is indented relative to the property name that's
413
+ # preserved.
414
+ if node.name_source_range
415
+ base_indentation = min_indentation[0...node.name_source_range.start_pos.offset - 1]
416
+ end
417
+
418
+ lines.first + "\n" + indented_lines.join("\n").gsub(/^#{base_indentation}/, ' ' * @tabs)
419
+ end
420
+
387
421
  def debug_info_rule(debug_info, options)
388
422
  node = Sass::Tree::DirectiveNode.resolved("@media -sass-debug-info")
389
- Sass::Util.hash_to_a(debug_info.map {|k, v| [k.to_s, v.to_s]}).each do |k, v|
423
+ debug_info.map {|k, v| [k.to_s, v.to_s]}.to_a.each do |k, v|
390
424
  rule = Sass::Tree::RuleNode.new([""])
391
425
  rule.resolved_rules = Sass::Selector::CommaSequence.new(
392
426
  [Sass::Selector::Sequence.new(
@@ -395,7 +429,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
395
429
  false)
396
430
  ])
397
431
  ])
398
- prop = Sass::Tree::PropNode.new([""], Sass::Script::Value::String.new(''), :new)
432
+ prop = Sass::Tree::PropNode.new([""], [""], :new)
399
433
  prop.resolved_name = "font-family"
400
434
  prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
401
435
  rule << prop