sass 3.4.19 → 3.4.20

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.
@@ -27,6 +27,17 @@ module Sass::Script::Tree
27
27
  # generate a warning.
28
28
  attr_reader :warn_for_color
29
29
 
30
+ # The type of interpolation deprecation for this node.
31
+ #
32
+ # This can be `:none`, indicating that the node doesn't use deprecated
33
+ # interpolation; `:immediate`, indicating that a deprecation warning should
34
+ # be emitted as soon as possible; or `:potential`, indicating that a
35
+ # deprecation warning should be emitted if the resulting string is used in a
36
+ # way that would distinguish it from a list.
37
+ #
38
+ # @return [Symbol]
39
+ attr_reader :deprecation
40
+
30
41
  # Interpolation in a property is of the form `before #{mid} after`.
31
42
  #
32
43
  # @param before [Node] See {Interpolation#before}
@@ -38,15 +49,16 @@ module Sass::Script::Tree
38
49
  # @param warn_for_color [Boolean] See {Interpolation#warn_for_color}
39
50
  # @comment
40
51
  # rubocop:disable ParameterLists
41
- def initialize(before, mid, after, wb, wa, originally_text = false, warn_for_color = false)
52
+ def initialize(before, mid, after, wb, wa, opts = {})
42
53
  # rubocop:enable ParameterLists
43
54
  @before = before
44
55
  @mid = mid
45
56
  @after = after
46
57
  @whitespace_before = wb
47
58
  @whitespace_after = wa
48
- @originally_text = originally_text
49
- @warn_for_color = warn_for_color
59
+ @originally_text = opts[:originally_text] || false
60
+ @warn_for_color = opts[:warn_for_color] || false
61
+ @deprecation = opts[:deprecation] || :none
50
62
  end
51
63
 
52
64
  # @return [String] A human-readable s-expression representation of the interpolation
@@ -56,6 +68,8 @@ module Sass::Script::Tree
56
68
 
57
69
  # @see Node#to_sass
58
70
  def to_sass(opts = {})
71
+ return to_quoted_equivalent.to_sass if deprecation == :immediate
72
+
59
73
  res = ""
60
74
  res << @before.to_sass(opts) if @before
61
75
  res << ' ' if @before && @whitespace_before
@@ -67,6 +81,19 @@ module Sass::Script::Tree
67
81
  res
68
82
  end
69
83
 
84
+ # Returns an `unquote()` expression that will evaluate to the same value as
85
+ # this interpolation.
86
+ #
87
+ # @return [Sass::Script::Tree::Node]
88
+ def to_quoted_equivalent
89
+ Funcall.new(
90
+ "unquote",
91
+ [to_string_interpolation(self)],
92
+ Sass::Util::NormalizedMap.new,
93
+ nil,
94
+ nil)
95
+ end
96
+
70
97
  # Returns the three components of the interpolation, `before`, `mid`, and `after`.
71
98
  #
72
99
  # @return [Array<Node>]
@@ -87,6 +114,49 @@ module Sass::Script::Tree
87
114
 
88
115
  protected
89
116
 
117
+ # Converts a script node into a corresponding string interpolation
118
+ # expression.
119
+ #
120
+ # @param node_or_interp [Sass::Script::Tree::Node]
121
+ # @return [Sass::Script::Tree::StringInterpolation]
122
+ def to_string_interpolation(node_or_interp)
123
+ unless node_or_interp.is_a?(Interpolation)
124
+ node = node_or_interp
125
+ return string_literal(node.value.to_s) if node.is_a?(Literal)
126
+ if node.is_a?(StringInterpolation)
127
+ return concat(string_literal(node.quote), concat(node, string_literal(node.quote)))
128
+ end
129
+ return StringInterpolation.new(string_literal(""), node, string_literal(""))
130
+ end
131
+
132
+ interp = node_or_interp
133
+ after_string_or_interp =
134
+ if interp.after
135
+ to_string_interpolation(interp.after)
136
+ else
137
+ string_literal("")
138
+ end
139
+ if interp.after && interp.whitespace_after
140
+ after_string_or_interp = concat(string_literal(' '), after_string_or_interp)
141
+ end
142
+
143
+ mid_string_or_interp = to_string_interpolation(interp.mid)
144
+
145
+ before_string_or_interp =
146
+ if interp.before
147
+ to_string_interpolation(interp.before)
148
+ else
149
+ string_literal("")
150
+ end
151
+ if interp.before && interp.whitespace_before
152
+ before_string_or_interp = concat(before_string_or_interp, string_literal(' '))
153
+ end
154
+
155
+ concat(before_string_or_interp, concat(mid_string_or_interp, after_string_or_interp))
156
+ end
157
+
158
+ private
159
+
90
160
  # Evaluates the interpolation.
91
161
  #
92
162
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
@@ -112,7 +182,42 @@ MESSAGE
112
182
  res << val.to_s(:quote => :none)
113
183
  res << " " if @after && @whitespace_after
114
184
  res << @after.perform(environment).to_s if @after
115
- opts(Sass::Script::Value::String.new(res))
185
+ str = Sass::Script::Value::String.new(
186
+ res, :identifier,
187
+ (to_quoted_equivalent.to_sass if deprecation == :potential))
188
+ str.source_range = source_range
189
+ opts(str)
190
+ end
191
+
192
+ # Concatenates two string literals or string interpolation expressions.
193
+ #
194
+ # @param string_or_interp1 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
195
+ # @param string_or_interp2 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
196
+ # @return [Sass::Script::Tree::StringInterpolation]
197
+ def concat(string_or_interp1, string_or_interp2)
198
+ if string_or_interp1.is_a?(Literal) && string_or_interp2.is_a?(Literal)
199
+ return string_literal(string_or_interp1.value.value + string_or_interp2.value.value)
200
+ end
201
+
202
+ if string_or_interp1.is_a?(Literal)
203
+ string = string_or_interp1
204
+ interp = string_or_interp2
205
+ before = string_literal(string.value.value + interp.before.value.value)
206
+ return StringInterpolation.new(before, interp.mid, interp.after)
207
+ end
208
+
209
+ StringInterpolation.new(
210
+ string_or_interp1.before,
211
+ string_or_interp1.mid,
212
+ concat(string_or_interp1.after, string_or_interp2))
213
+ end
214
+
215
+ # Returns a string literal with the given contents.
216
+ #
217
+ # @param string [String]
218
+ # @return string [Sass::Script::Tree::Literal]
219
+ def string_literal(string)
220
+ Literal.new(Sass::Script::Value::String.new(string, :string))
116
221
  end
117
222
  end
118
223
  end
@@ -24,6 +24,12 @@ module Sass::Script::Tree
24
24
  @before.value.type
25
25
  end
26
26
 
27
+ # Returns the quote character that should be used to wrap a Sass
28
+ # representation of this interpolation.
29
+ def quote
30
+ quote_for(self) || '"'
31
+ end
32
+
27
33
  # Interpolation in a string is of the form `"before #{mid} after"`,
28
34
  # where `before` and `after` may include more interpolation.
29
35
  #
@@ -289,6 +289,9 @@ module Sass::Script::Value
289
289
  # and confusing.
290
290
  str = ("%0.#{self.class.precision}f" % value).gsub(/0*$/, '') if str.include?('e')
291
291
 
292
+ if @options && options[:style] == :compressed
293
+ str.sub!(/^(-)?0\./, '\1.')
294
+ end
292
295
  unitless? ? str : "#{str}#{unit_str}"
293
296
  end
294
297
  alias_method :to_sass, :inspect
@@ -478,6 +481,7 @@ module Sass::Script::Value
478
481
  'cm' => Rational(1, 2.54),
479
482
  'pc' => Rational(1, 6),
480
483
  'mm' => Rational(1, 25.4),
484
+ 'q' => Rational(1, 101.6),
481
485
  'pt' => Rational(1, 72),
482
486
  'px' => Rational(1, 96)
483
487
  },
@@ -76,9 +76,13 @@ module Sass::Script::Value
76
76
  #
77
77
  # @param value [String] See \{#value}
78
78
  # @param type [Symbol] See \{#type}
79
- def initialize(value, type = :identifier)
79
+ # @param deprecated_interp_equivalent [String?]
80
+ # If this was created via a potentially-deprecated string interpolation,
81
+ # this is the replacement expression that should be suggested to the user.
82
+ def initialize(value, type = :identifier, deprecated_interp_equivalent = nil)
80
83
  super(value)
81
84
  @type = type
85
+ @deprecated_interp_equivalent = deprecated_interp_equivalent
82
86
  end
83
87
 
84
88
  # @see Value#plus
@@ -102,6 +106,38 @@ module Sass::Script::Value
102
106
  to_s(opts.merge(:sass => true))
103
107
  end
104
108
 
109
+ def separator
110
+ check_deprecated_interp
111
+ super
112
+ end
113
+
114
+ def to_a
115
+ check_deprecated_interp
116
+ super
117
+ end
118
+
119
+ # Prints a warning if this string was created using potentially-deprecated
120
+ # interpolation.
121
+ def check_deprecated_interp
122
+ return unless @deprecated_interp_equivalent
123
+
124
+ # rubocop:disable GlobalVars
125
+ $_sass_deprecated_interp_warnings ||= Set.new
126
+ key = [source_range.start_pos.line, source_range.file, @deprecated_interp_equivalent]
127
+ return if $_sass_deprecated_interp_warnings.include?(key)
128
+ $_sass_deprecated_interp_warnings << key
129
+ # rubocop:enable GlobalVars
130
+
131
+ location = "on line #{source_range.start_pos.line}"
132
+ location << " of #{source_range.file}" if source_range.file
133
+ Sass::Util.sass_warn <<WARNING
134
+ DEPRECATION WARNING #{location}: \#{} interpolation near operators will be simplified
135
+ in a future version of Sass. To preserve the current behavior, use quotes:
136
+
137
+ #{@deprecated_interp_equivalent}
138
+ WARNING
139
+ end
140
+
105
141
  def inspect
106
142
  String.quote(value)
107
143
  end
@@ -55,6 +55,15 @@ module Sass
55
55
  interp_ident
56
56
  end
57
57
 
58
+ # Parses a supports clause for an @import directive
59
+ def parse_supports_clause
60
+ init_scanner!
61
+ ss
62
+ clause = supports_clause
63
+ ss
64
+ clause
65
+ end
66
+
58
67
  # Parses a media query list.
59
68
  #
60
69
  # @return [Sass::Media::QueryList] The parsed query list
@@ -150,20 +159,16 @@ module Sass
150
159
  silent = text =~ %r{\A//}
151
160
  loud = !silent && text =~ %r{\A/[/*]!}
152
161
  line = @line - text.count("\n")
162
+ comment_start = @scanner.pos - text.length
163
+ index_before_line = @scanner.string.rindex("\n", comment_start) || -1
164
+ offset = comment_start - index_before_line
153
165
 
154
166
  if silent
155
167
  value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
156
168
  else
157
- value = Sass::Engine.parse_interp(
158
- text, line, @scanner.pos - text.size, :filename => @filename)
159
- newline_before_comment = @scanner.string.rindex("\n", @scanner.pos - text.length)
160
- last_line_before_comment =
161
- if newline_before_comment
162
- @scanner.string[newline_before_comment + 1...@scanner.pos - text.length]
163
- else
164
- @scanner.string[0...@scanner.pos - text.length]
165
- end
166
- value.unshift(last_line_before_comment.gsub(/[^\s]/, ' '))
169
+ value = Sass::Engine.parse_interp(text, line, offset, :filename => @filename)
170
+ line_before_comment = @scanner.string[index_before_line + 1...comment_start]
171
+ value.unshift(line_before_comment.gsub(/[^\s]/, ' '))
167
172
  end
168
173
 
169
174
  type = if silent
@@ -173,8 +178,8 @@ module Sass
173
178
  else
174
179
  :normal
175
180
  end
176
- comment = Sass::Tree::CommentNode.new(value, type)
177
- comment.line = line
181
+ start_pos = Sass::Source::Position.new(line, offset)
182
+ comment = node(Sass::Tree::CommentNode.new(value, type), start_pos)
178
183
  node << comment
179
184
  end
180
185
 
@@ -379,16 +384,20 @@ module Sass
379
384
  if uri
380
385
  str = sass_script(:parse_string)
381
386
  ss
387
+ supports = supports_clause
388
+ ss
382
389
  media = media_query_list
383
390
  ss
384
- return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
391
+ return node(Tree::CssImportNode.new(str, media.to_a, supports), start_pos)
385
392
  end
386
393
  ss
387
394
 
395
+ supports = supports_clause
396
+ ss
388
397
  media = media_query_list
389
- if str =~ %r{^(https?:)?//} || media || use_css_import?
398
+ if str =~ %r{^(https?:)?//} || media || supports || use_css_import?
390
399
  return node(Sass::Tree::CssImportNode.new(
391
- Sass::Script::Value::String.quote(str), media.to_a), start_pos)
400
+ Sass::Script::Value::String.quote(str), media.to_a, supports), start_pos)
392
401
  end
393
402
 
394
403
  node(Sass::Tree::ImportNode.new(str.strip), start_pos)
@@ -548,6 +557,15 @@ module Sass
548
557
  node(node, start_pos)
549
558
  end
550
559
 
560
+ def supports_clause
561
+ return unless tok(/supports\(/i)
562
+ ss
563
+ supports = supports_condition
564
+ ss
565
+ tok!(/\)/)
566
+ supports
567
+ end
568
+
551
569
  def supports_condition
552
570
  supports_negation || supports_operator || supports_interpolation
553
571
  end
@@ -166,7 +166,7 @@ module Sass
166
166
  extended.members.reject! {|seq| seq.has_placeholder?}
167
167
 
168
168
  # For `:not()`, we usually want to get rid of any complex
169
- # selectors becuase that will cause the selector to fail to
169
+ # selectors because that will cause the selector to fail to
170
170
  # parse on all browsers at time of writing. We can keep them
171
171
  # if either the original selector had a complex selector, or
172
172
  # the result of extending has only complex selectors,
@@ -15,6 +15,11 @@ module Sass::Tree
15
15
  # @return [String]
16
16
  attr_accessor :resolved_uri
17
17
 
18
+ # The supports condition for this import.
19
+ #
20
+ # @return [Sass::Supports::Condition]
21
+ attr_accessor :supports_condition
22
+
18
23
  # The media query for this rule, interspersed with
19
24
  # {Sass::Script::Tree::Node}s representing `#{}`-interpolation. Any adjacent
20
25
  # strings will be merged together.
@@ -30,9 +35,11 @@ module Sass::Tree
30
35
 
31
36
  # @param uri [String, Sass::Script::Tree::Node] See \{#uri}
32
37
  # @param query [Array<String, Sass::Script::Tree::Node>] See \{#query}
33
- def initialize(uri, query = [])
38
+ # @param supports_condition [Sass::Supports::Condition] See \{#supports_condition}
39
+ def initialize(uri, query = [], supports_condition = nil)
34
40
  @uri = uri
35
41
  @query = query
42
+ @supports_condition = supports_condition
36
43
  super('')
37
44
  end
38
45
 
@@ -52,6 +59,7 @@ module Sass::Tree
52
59
  @resolved_value ||=
53
60
  begin
54
61
  str = "@import #{resolved_uri}"
62
+ str << " supports(#{supports_condition.to_css})" if supports_condition
55
63
  str << " #{resolved_query.to_css}" if resolved_query
56
64
  str
57
65
  end
@@ -163,6 +163,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
163
163
  else
164
164
  str = "#{tab_str}@import #{node.uri}"
165
165
  end
166
+ str << " supports(#{node.supports_condition.to_src(@options)})" if node.supports_condition
166
167
  str << " #{interp_to_src(node.query)}" unless node.query.empty?
167
168
  "#{str}#{semi}\n"
168
169
  end
@@ -526,6 +526,9 @@ WARNING
526
526
  node.filename, node.options[:importer], node.line)
527
527
  node.resolved_query ||= parser.parse_media_query_list
528
528
  end
529
+ if node.supports_condition
530
+ node.supports_condition.perform(@environment)
531
+ end
529
532
  yield
530
533
  end
531
534
 
@@ -159,8 +159,9 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
159
159
  def visit_comment(node)
160
160
  return if node.invisible?
161
161
  spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max)
162
+ output(spaces)
162
163
 
163
- content = node.resolved_value.gsub(/^/, spaces)
164
+ content = node.resolved_value.split("\n").join("\n" + spaces)
164
165
  if node.type == :silent
165
166
  content.gsub!(%r{^(\s*)//(.*)$}) {|md| "#{$1}/*#{$2} */"}
166
167
  end
@@ -297,7 +298,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
297
298
  rule_part = seq.to_s
298
299
  if node.style == :compressed
299
300
  rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
300
- rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
301
+ rule_part.gsub!(/\s*([-,+>])\s*/m, '\1')
301
302
  rule_part.strip!
302
303
  end
303
304
  rule_part
data/lib/sass/util.rb CHANGED
@@ -144,9 +144,10 @@ module Sass
144
144
  # @param value [Numeric]
145
145
  # @return [Numeric]
146
146
  def round(value)
147
- # If the number is within epsilon of X.5, round up.
148
- return value.ceil if (value % 1) - 0.5 > -0.00001
149
- value.round
147
+ # If the number is within epsilon of X.5, round up (or down for negative
148
+ # numbers).
149
+ return value.round if (value % 1) - 0.5 <= -1 * Script::Value::Number.epsilon
150
+ value > 0 ? value.ceil : value.floor
150
151
  end
151
152
 
152
153
  # Concatenates all strings that are adjacent in an array,
@@ -2112,6 +2112,14 @@ foo {
2112
2112
  SCSS
2113
2113
  end
2114
2114
 
2115
+ def test_import_with_supports_clause
2116
+ assert_renders(<<'SASS', <<'SCSS')
2117
+ @import url("fallback-layout.css") supports(not (display: #{$display-type}))
2118
+ SASS
2119
+ @import url("fallback-layout.css") supports(not (display: #{$display-type}));
2120
+ SCSS
2121
+ end
2122
+
2115
2123
  private
2116
2124
 
2117
2125
  def assert_sass_to_sass(sass, options = {})