sass 3.4.19 → 3.4.20

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = {})