sass 3.4.19 → 3.4.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.yardopts +2 -0
- data/Rakefile +10 -3
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/extra/update_watch.rb +1 -1
- data/lib/sass/css.rb +1 -1
- data/lib/sass/engine.rb +36 -10
- data/lib/sass/exec/base.rb +1 -1
- data/lib/sass/importers/base.rb +1 -1
- data/lib/sass/plugin/configuration.rb +37 -21
- data/lib/sass/plugin/merb.rb +1 -1
- data/lib/sass/plugin/rails.rb +1 -1
- data/lib/sass/script/functions.rb +9 -2
- data/lib/sass/script/lexer.rb +7 -0
- data/lib/sass/script/parser.rb +154 -17
- data/lib/sass/script/tree/interpolation.rb +109 -4
- data/lib/sass/script/tree/string_interpolation.rb +6 -0
- data/lib/sass/script/value/number.rb +4 -0
- data/lib/sass/script/value/string.rb +37 -1
- data/lib/sass/scss/parser.rb +33 -15
- data/lib/sass/selector/simple_sequence.rb +1 -1
- data/lib/sass/tree/css_import_node.rb +9 -1
- data/lib/sass/tree/visitors/convert.rb +1 -0
- data/lib/sass/tree/visitors/perform.rb +3 -0
- data/lib/sass/tree/visitors/to_css.rb +3 -2
- data/lib/sass/util.rb +4 -3
- data/test/sass/conversion_test.rb +8 -0
- data/test/sass/engine_test.rb +28 -0
- data/test/sass/functions_test.rb +3 -0
- data/test/sass/script_conversion_test.rb +27 -26
- data/test/sass/script_test.rb +197 -37
- data/test/sass/scss/scss_test.rb +19 -0
- data/test/sass/source_map_test.rb +130 -64
- data/test/sass/util_test.rb +11 -0
- data/test/test_helper.rb +4 -3
- metadata +2 -2
@@ -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,
|
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
|
-
|
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
|
-
|
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
|
data/lib/sass/scss/parser.rb
CHANGED
@@ -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
|
-
|
159
|
-
|
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
|
-
|
177
|
-
comment
|
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
|
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
|
-
|
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
|
@@ -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.
|
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*([
|
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
|
-
|
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 = {})
|