sass 3.4.0 → 3.4.25
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/.yardopts +3 -1
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +148 -0
- data/MIT-LICENSE +1 -1
- data/README.md +26 -20
- data/Rakefile +103 -20
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/extra/sass-spec-ref.sh +32 -0
- data/extra/update_watch.rb +1 -1
- data/lib/sass/cache_stores/filesystem.rb +7 -7
- data/lib/sass/cache_stores/memory.rb +4 -5
- data/lib/sass/callbacks.rb +2 -2
- data/lib/sass/css.rb +11 -10
- data/lib/sass/deprecation.rb +55 -0
- data/lib/sass/engine.rb +83 -38
- data/lib/sass/environment.rb +26 -2
- data/lib/sass/error.rb +12 -12
- data/lib/sass/exec/base.rb +15 -3
- data/lib/sass/exec/sass_convert.rb +34 -15
- data/lib/sass/exec/sass_scss.rb +23 -7
- data/lib/sass/features.rb +2 -2
- data/lib/sass/importers/base.rb +1 -1
- data/lib/sass/importers/deprecated_path.rb +51 -0
- data/lib/sass/importers/filesystem.rb +24 -16
- data/lib/sass/importers.rb +1 -0
- data/lib/sass/logger/base.rb +8 -2
- data/lib/sass/logger/delayed.rb +50 -0
- data/lib/sass/logger.rb +8 -3
- data/lib/sass/plugin/compiler.rb +42 -25
- data/lib/sass/plugin/configuration.rb +38 -22
- data/lib/sass/plugin/merb.rb +2 -2
- data/lib/sass/plugin/rack.rb +3 -3
- data/lib/sass/plugin/rails.rb +1 -1
- data/lib/sass/plugin/staleness_checker.rb +3 -3
- data/lib/sass/plugin.rb +3 -2
- data/lib/sass/script/css_parser.rb +2 -3
- data/lib/sass/script/css_variable_warning.rb +52 -0
- data/lib/sass/script/functions.rb +140 -73
- data/lib/sass/script/lexer.rb +37 -22
- data/lib/sass/script/parser.rb +235 -40
- data/lib/sass/script/tree/funcall.rb +12 -5
- data/lib/sass/script/tree/interpolation.rb +109 -4
- data/lib/sass/script/tree/list_literal.rb +31 -4
- data/lib/sass/script/tree/literal.rb +4 -0
- data/lib/sass/script/tree/node.rb +21 -3
- data/lib/sass/script/tree/operation.rb +54 -1
- data/lib/sass/script/tree/string_interpolation.rb +58 -37
- data/lib/sass/script/tree/variable.rb +1 -1
- data/lib/sass/script/value/base.rb +10 -9
- data/lib/sass/script/value/color.rb +42 -24
- data/lib/sass/script/value/helpers.rb +16 -6
- data/lib/sass/script/value/map.rb +1 -1
- data/lib/sass/script/value/number.rb +52 -19
- data/lib/sass/script/value/string.rb +46 -5
- data/lib/sass/script.rb +3 -3
- data/lib/sass/scss/css_parser.rb +16 -2
- data/lib/sass/scss/parser.rb +120 -75
- data/lib/sass/scss/rx.rb +9 -10
- data/lib/sass/scss/static_parser.rb +19 -14
- data/lib/sass/scss.rb +0 -2
- data/lib/sass/selector/abstract_sequence.rb +8 -6
- data/lib/sass/selector/comma_sequence.rb +25 -9
- data/lib/sass/selector/pseudo.rb +45 -35
- data/lib/sass/selector/sequence.rb +54 -18
- data/lib/sass/selector/simple.rb +11 -11
- data/lib/sass/selector/simple_sequence.rb +34 -15
- data/lib/sass/selector.rb +7 -10
- data/lib/sass/shared.rb +1 -1
- data/lib/sass/source/map.rb +7 -4
- data/lib/sass/source/position.rb +4 -4
- data/lib/sass/stack.rb +2 -2
- data/lib/sass/supports.rb +8 -10
- data/lib/sass/tree/comment_node.rb +1 -1
- data/lib/sass/tree/css_import_node.rb +9 -1
- data/lib/sass/tree/function_node.rb +8 -3
- data/lib/sass/tree/import_node.rb +6 -5
- data/lib/sass/tree/node.rb +5 -3
- data/lib/sass/tree/prop_node.rb +5 -6
- data/lib/sass/tree/rule_node.rb +14 -4
- data/lib/sass/tree/visitors/check_nesting.rb +18 -22
- data/lib/sass/tree/visitors/convert.rb +43 -26
- data/lib/sass/tree/visitors/cssize.rb +5 -1
- data/lib/sass/tree/visitors/deep_copy.rb +1 -1
- data/lib/sass/tree/visitors/extend.rb +15 -13
- data/lib/sass/tree/visitors/perform.rb +42 -17
- data/lib/sass/tree/visitors/set_options.rb +1 -1
- data/lib/sass/tree/visitors/to_css.rb +58 -30
- data/lib/sass/util/multibyte_string_scanner.rb +0 -2
- data/lib/sass/util/normalized_map.rb +0 -1
- data/lib/sass/util/subset_map.rb +1 -2
- data/lib/sass/util.rb +125 -68
- data/lib/sass/version.rb +2 -2
- data/lib/sass.rb +10 -3
- data/test/sass/compiler_test.rb +6 -2
- data/test/sass/conversion_test.rb +187 -53
- data/test/sass/css2sass_test.rb +50 -1
- data/test/sass/css_variable_test.rb +132 -0
- data/test/sass/engine_test.rb +207 -61
- data/test/sass/exec_test.rb +10 -0
- data/test/sass/extend_test.rb +101 -29
- data/test/sass/functions_test.rb +60 -9
- data/test/sass/importer_test.rb +9 -0
- data/test/sass/more_templates/more1.sass +10 -10
- data/test/sass/more_templates/more_import.sass +2 -2
- data/test/sass/plugin_test.rb +10 -8
- data/test/sass/results/script.css +3 -3
- data/test/sass/script_conversion_test.rb +58 -29
- data/test/sass/script_test.rb +430 -53
- data/test/sass/scss/css_test.rb +73 -7
- data/test/sass/scss/rx_test.rb +4 -0
- data/test/sass/scss/scss_test.rb +309 -4
- data/test/sass/source_map_test.rb +152 -74
- data/test/sass/superselector_test.rb +19 -0
- data/test/sass/templates/_partial.sass +1 -1
- data/test/sass/templates/basic.sass +10 -10
- data/test/sass/templates/bork1.sass +1 -1
- data/test/sass/templates/bork5.sass +1 -1
- data/test/sass/templates/compact.sass +10 -10
- data/test/sass/templates/complex.sass +187 -187
- data/test/sass/templates/compressed.sass +10 -10
- data/test/sass/templates/expanded.sass +10 -10
- data/test/sass/templates/import.sass +2 -2
- data/test/sass/templates/importee.sass +3 -3
- data/test/sass/templates/mixins.sass +22 -22
- data/test/sass/templates/multiline.sass +4 -4
- data/test/sass/templates/nested.sass +13 -13
- data/test/sass/templates/parent_ref.sass +12 -12
- data/test/sass/templates/script.sass +70 -70
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
- data/test/sass/templates/subdir/subdir.sass +3 -3
- data/test/sass/templates/units.sass +10 -10
- data/test/sass/util/multibyte_string_scanner_test.rb +10 -2
- data/test/sass/util_test.rb +15 -44
- data/test/sass-spec.yml +3 -0
- data/test/test_helper.rb +5 -4
- metadata +302 -295
- data/CONTRIBUTING +0 -3
- data/lib/sass/scss/script_lexer.rb +0 -15
- data/lib/sass/scss/script_parser.rb +0 -25
data/lib/sass/script/lexer.rb
CHANGED
@@ -19,13 +19,13 @@ module Sass
|
|
19
19
|
# `source_range`: \[`Sass::Source::Range`\]
|
20
20
|
# : The range in the source file in which the token appeared.
|
21
21
|
#
|
22
|
-
# `pos`: \[`
|
22
|
+
# `pos`: \[`Integer`\]
|
23
23
|
# : The scanner position at which the SassScript token appeared.
|
24
24
|
Token = Struct.new(:type, :value, :source_range, :pos)
|
25
25
|
|
26
26
|
# The line number of the lexer's current position.
|
27
27
|
#
|
28
|
-
# @return [
|
28
|
+
# @return [Integer]
|
29
29
|
def line
|
30
30
|
return @line unless @tok
|
31
31
|
@tok.source_range.start_pos.line
|
@@ -34,7 +34,7 @@ module Sass
|
|
34
34
|
# The number of bytes into the current line
|
35
35
|
# of the lexer's current position (1-based).
|
36
36
|
#
|
37
|
-
# @return [
|
37
|
+
# @return [Integer]
|
38
38
|
def offset
|
39
39
|
return @offset unless @tok
|
40
40
|
@tok.source_range.start_pos.offset
|
@@ -71,8 +71,8 @@ module Sass
|
|
71
71
|
OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
|
72
72
|
|
73
73
|
TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge(
|
74
|
-
|
75
|
-
|
74
|
+
:const => "variable (e.g. $foo)",
|
75
|
+
:ident => "identifier (e.g. middle)")
|
76
76
|
|
77
77
|
# A list of operator strings ordered with longer names first
|
78
78
|
# so that `>` and `<` don't clobber `>=` and `<=`.
|
@@ -80,7 +80,7 @@ module Sass
|
|
80
80
|
|
81
81
|
# A sub-list of {OP_NAMES} that only includes operators
|
82
82
|
# with identifier names.
|
83
|
-
IDENT_OP_NAMES = OP_NAMES.select {|k,
|
83
|
+
IDENT_OP_NAMES = OP_NAMES.select {|k, _v| k =~ /^\w+/}
|
84
84
|
|
85
85
|
PARSEABLE_NUMBER = /(?:(\d*\.\d+)|(\d+))(?:[eE]([+-]?\d+))?(#{UNIT})?/
|
86
86
|
|
@@ -142,12 +142,12 @@ module Sass
|
|
142
142
|
}
|
143
143
|
|
144
144
|
# @param str [String, StringScanner] The source text to lex
|
145
|
-
# @param line [
|
145
|
+
# @param line [Integer] The 1-based line on which the SassScript appears.
|
146
146
|
# Used for error reporting and sourcemap building
|
147
|
-
# @param offset [
|
147
|
+
# @param offset [Integer] The 1-based character (not byte) offset in the line in the source.
|
148
148
|
# Used for error reporting and sourcemap building
|
149
149
|
# @param options [{Symbol => Object}] An options hash;
|
150
|
-
# see {file:SASS_REFERENCE.md#
|
150
|
+
# see {file:SASS_REFERENCE.md#Options the Sass options documentation}
|
151
151
|
def initialize(str, line, offset, options)
|
152
152
|
@scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
|
153
153
|
@line = line
|
@@ -155,6 +155,8 @@ module Sass
|
|
155
155
|
@options = options
|
156
156
|
@interpolation_stack = []
|
157
157
|
@prev = nil
|
158
|
+
@tok = nil
|
159
|
+
@next_tok = nil
|
158
160
|
end
|
159
161
|
|
160
162
|
# Moves the lexer forward one token.
|
@@ -179,6 +181,13 @@ module Sass
|
|
179
181
|
end
|
180
182
|
end
|
181
183
|
|
184
|
+
# Returns the given character.
|
185
|
+
#
|
186
|
+
# @return [String]
|
187
|
+
def char(pos = @scanner.pos)
|
188
|
+
@scanner.string[pos, 1]
|
189
|
+
end
|
190
|
+
|
182
191
|
# Returns the next token without moving the lexer forward.
|
183
192
|
#
|
184
193
|
# @return [Token] The next token
|
@@ -189,16 +198,16 @@ module Sass
|
|
189
198
|
# Rewinds the underlying StringScanner
|
190
199
|
# to before the token returned by \{#peek}.
|
191
200
|
def unpeek!
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
201
|
+
return unless @tok
|
202
|
+
@scanner.pos = @tok.pos
|
203
|
+
@line = @tok.source_range.start_pos.line
|
204
|
+
@offset = @tok.source_range.start_pos.offset
|
197
205
|
end
|
198
206
|
|
199
207
|
# @return [Boolean] Whether or not there's more source text to lex.
|
200
208
|
def done?
|
201
|
-
|
209
|
+
return if @next_tok
|
210
|
+
whitespace unless after_interpolation? && !@interpolation_stack.empty?
|
202
211
|
@scanner.eos? && @tok.nil?
|
203
212
|
end
|
204
213
|
|
@@ -235,6 +244,11 @@ module Sass
|
|
235
244
|
private
|
236
245
|
|
237
246
|
def read_token
|
247
|
+
if (tok = @next_tok)
|
248
|
+
@next_tok = nil
|
249
|
+
return tok
|
250
|
+
end
|
251
|
+
|
238
252
|
return if done?
|
239
253
|
start_pos = source_position
|
240
254
|
value = token
|
@@ -293,9 +307,9 @@ MESSAGE
|
|
293
307
|
end
|
294
308
|
|
295
309
|
if @scanner[2] == '#{' # '
|
296
|
-
@scanner.pos -= 2 # Don't actually consume the #{
|
297
|
-
@offset -= 2
|
298
310
|
@interpolation_stack << [:string, re]
|
311
|
+
start_pos = Sass::Source::Position.new(@line, @offset - 2)
|
312
|
+
@next_tok = Token.new(:string_interpolation, range(start_pos), @scanner.pos - 2)
|
299
313
|
end
|
300
314
|
str =
|
301
315
|
if re == :uri
|
@@ -392,9 +406,9 @@ MESSAGE
|
|
392
406
|
else
|
393
407
|
raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
|
394
408
|
str.slice!(-2..-1)
|
395
|
-
@scanner.pos -= 2 # Don't actually consume the #{
|
396
|
-
@offset -= 2
|
397
409
|
@interpolation_stack << [:special_fun, parens]
|
410
|
+
start_pos = Sass::Source::Position.new(@line, @offset - 2)
|
411
|
+
@next_tok = Token.new(:string_interpolation, range(start_pos), @scanner.pos - 2)
|
398
412
|
end
|
399
413
|
|
400
414
|
return [:special_fun, Sass::Script::Value::String.new(str)]
|
@@ -405,7 +419,7 @@ MESSAGE
|
|
405
419
|
end
|
406
420
|
|
407
421
|
def special_val
|
408
|
-
return unless scan(
|
422
|
+
return unless scan(/!#{W}important/i)
|
409
423
|
[:string, Script::Value::String.new("!important")]
|
410
424
|
end
|
411
425
|
|
@@ -418,8 +432,9 @@ MESSAGE
|
|
418
432
|
def op
|
419
433
|
op = scan(REGULAR_EXPRESSIONS[:op])
|
420
434
|
return unless op
|
421
|
-
|
422
|
-
|
435
|
+
name = OPERATORS[op]
|
436
|
+
@interpolation_stack << nil if name == :begin_interpolation
|
437
|
+
[name]
|
423
438
|
end
|
424
439
|
|
425
440
|
def raw(rx)
|
data/lib/sass/script/parser.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'sass/script/lexer'
|
2
|
+
require 'sass/script/css_variable_warning'
|
2
3
|
|
3
4
|
module Sass
|
4
5
|
module Script
|
@@ -7,28 +8,34 @@ module Sass
|
|
7
8
|
class Parser
|
8
9
|
# The line number of the parser's current position.
|
9
10
|
#
|
10
|
-
# @return [
|
11
|
+
# @return [Integer]
|
11
12
|
def line
|
12
13
|
@lexer.line
|
13
14
|
end
|
14
15
|
|
15
16
|
# The column number of the parser's current position.
|
16
17
|
#
|
17
|
-
# @return [
|
18
|
+
# @return [Integer]
|
18
19
|
def offset
|
19
20
|
@lexer.offset
|
20
21
|
end
|
21
22
|
|
22
23
|
# @param str [String, StringScanner] The source text to parse
|
23
|
-
# @param line [
|
24
|
+
# @param line [Integer] The line on which the SassScript appears.
|
24
25
|
# Used for error reporting and sourcemap building
|
25
|
-
# @param offset [
|
26
|
+
# @param offset [Integer] The character (not byte) offset where the script starts in the line.
|
26
27
|
# Used for error reporting and sourcemap building
|
27
|
-
# @param options [{Symbol => Object}] An options hash;
|
28
|
-
#
|
28
|
+
# @param options [{Symbol => Object}] An options hash; see
|
29
|
+
# {file:SASS_REFERENCE.md#Options the Sass options documentation}.
|
30
|
+
# This supports an additional `:allow_extra_text` option that controls
|
31
|
+
# whether the parser throws an error when extra text is encountered
|
32
|
+
# after the parsed construct.
|
29
33
|
def initialize(str, line, offset, options = {})
|
30
34
|
@options = options
|
35
|
+
@allow_extra_text = options.delete(:allow_extra_text)
|
31
36
|
@lexer = lexer_class.new(str, line, offset, options)
|
37
|
+
@stop_at = nil
|
38
|
+
@css_variable_warning = nil
|
32
39
|
end
|
33
40
|
|
34
41
|
# Parses a SassScript expression within an interpolated segment (`#{}`).
|
@@ -46,7 +53,8 @@ module Sass
|
|
46
53
|
expr = assert_expr :expr
|
47
54
|
assert_tok :end_interpolation
|
48
55
|
expr = Sass::Script::Tree::Interpolation.new(
|
49
|
-
nil, expr, nil,
|
56
|
+
nil, expr, nil, false, false, :warn_for_color => warn_for_color)
|
57
|
+
check_for_interpolation expr
|
50
58
|
expr.options = @options
|
51
59
|
node(expr, start_pos)
|
52
60
|
rescue Sass::SyntaxError => e
|
@@ -56,12 +64,23 @@ module Sass
|
|
56
64
|
|
57
65
|
# Parses a SassScript expression.
|
58
66
|
#
|
67
|
+
# @param css_variable [Boolean] Whether this is the value of a CSS variable.
|
59
68
|
# @return [Script::Tree::Node] The root node of the parse tree
|
60
69
|
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
|
61
|
-
def parse
|
70
|
+
def parse(css_variable = false)
|
71
|
+
if css_variable
|
72
|
+
@css_variable_warning = CssVariableWarning.new
|
73
|
+
end
|
74
|
+
|
62
75
|
expr = assert_expr :expr
|
63
76
|
assert_done
|
64
77
|
expr.options = @options
|
78
|
+
check_for_interpolation expr
|
79
|
+
|
80
|
+
if css_variable
|
81
|
+
@css_variable_warning.value = expr
|
82
|
+
end
|
83
|
+
|
65
84
|
expr
|
66
85
|
rescue Sass::SyntaxError => e
|
67
86
|
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
@@ -79,6 +98,7 @@ module Sass
|
|
79
98
|
expr = assert_expr :expr
|
80
99
|
assert_done
|
81
100
|
expr.options = @options
|
101
|
+
check_for_interpolation expr
|
82
102
|
expr
|
83
103
|
rescue Sass::SyntaxError => e
|
84
104
|
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
@@ -102,10 +122,26 @@ module Sass
|
|
102
122
|
end
|
103
123
|
assert_done
|
104
124
|
|
105
|
-
args.each
|
106
|
-
|
107
|
-
|
108
|
-
|
125
|
+
args.each do |a|
|
126
|
+
check_for_interpolation a
|
127
|
+
a.options = @options
|
128
|
+
end
|
129
|
+
|
130
|
+
keywords.each do |_k, v|
|
131
|
+
check_for_interpolation v
|
132
|
+
v.options = @options
|
133
|
+
end
|
134
|
+
|
135
|
+
if splat
|
136
|
+
check_for_interpolation splat
|
137
|
+
splat.options = @options
|
138
|
+
end
|
139
|
+
|
140
|
+
if kwarg_splat
|
141
|
+
check_for_interpolation kwarg_splat
|
142
|
+
kwarg_splat.options = @options
|
143
|
+
end
|
144
|
+
|
109
145
|
return args, keywords, splat, kwarg_splat
|
110
146
|
rescue Sass::SyntaxError => e
|
111
147
|
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
@@ -122,10 +158,20 @@ module Sass
|
|
122
158
|
assert_done
|
123
159
|
|
124
160
|
args.each do |k, v|
|
161
|
+
check_for_interpolation k
|
125
162
|
k.options = @options
|
126
|
-
|
163
|
+
|
164
|
+
if v
|
165
|
+
check_for_interpolation v
|
166
|
+
v.options = @options
|
167
|
+
end
|
127
168
|
end
|
128
|
-
|
169
|
+
|
170
|
+
if splat
|
171
|
+
check_for_interpolation splat
|
172
|
+
splat.options = @options
|
173
|
+
end
|
174
|
+
|
129
175
|
return args, splat
|
130
176
|
rescue Sass::SyntaxError => e
|
131
177
|
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
@@ -142,10 +188,20 @@ module Sass
|
|
142
188
|
assert_done
|
143
189
|
|
144
190
|
args.each do |k, v|
|
191
|
+
check_for_interpolation k
|
145
192
|
k.options = @options
|
146
|
-
|
193
|
+
|
194
|
+
if v
|
195
|
+
check_for_interpolation v
|
196
|
+
v.options = @options
|
197
|
+
end
|
147
198
|
end
|
148
|
-
|
199
|
+
|
200
|
+
if splat
|
201
|
+
check_for_interpolation splat
|
202
|
+
splat.options = @options
|
203
|
+
end
|
204
|
+
|
149
205
|
return args, splat
|
150
206
|
rescue Sass::SyntaxError => e
|
151
207
|
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
@@ -165,6 +221,7 @@ module Sass
|
|
165
221
|
end
|
166
222
|
|
167
223
|
expr = assert_expr :funcall
|
224
|
+
check_for_interpolation expr
|
168
225
|
expr.options = @options
|
169
226
|
@lexer.unpeek!
|
170
227
|
expr
|
@@ -175,12 +232,12 @@ module Sass
|
|
175
232
|
|
176
233
|
# Parses a SassScript expression.
|
177
234
|
#
|
178
|
-
# @overload parse(str, line, offset, filename = nil)
|
179
235
|
# @return [Script::Tree::Node] The root node of the parse tree
|
180
236
|
# @see Parser#initialize
|
181
237
|
# @see Parser#parse
|
182
|
-
def self.parse(
|
183
|
-
|
238
|
+
def self.parse(value, line, offset, options = {})
|
239
|
+
css_variable = options.delete :css_variable
|
240
|
+
new(value, line, offset, options).parse(css_variable)
|
184
241
|
end
|
185
242
|
|
186
243
|
PRECEDENCE = [
|
@@ -193,6 +250,8 @@ module Sass
|
|
193
250
|
|
194
251
|
ASSOCIATIVE = [:plus, :times]
|
195
252
|
|
253
|
+
VALID_CSS_OPS = [:comma, :single_eq, :space, :div]
|
254
|
+
|
196
255
|
class << self
|
197
256
|
# Returns an integer representing the precedence
|
198
257
|
# of the given operator.
|
@@ -232,6 +291,10 @@ module Sass
|
|
232
291
|
return other_interp
|
233
292
|
end
|
234
293
|
|
294
|
+
if @css_variable_warning && !VALID_CSS_OPS.include?(tok.type)
|
295
|
+
@css_variable_warning.warn!
|
296
|
+
end
|
297
|
+
|
235
298
|
e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type),
|
236
299
|
e.source_range.start_pos)
|
237
300
|
end
|
@@ -247,6 +310,8 @@ RUBY
|
|
247
310
|
interp = try_op_before_interp(tok)
|
248
311
|
return interp if interp
|
249
312
|
start_pos = source_position
|
313
|
+
|
314
|
+
@css_variable_warning.warn! if @css_variable_warning
|
250
315
|
node(Tree::UnaryOperation.new(assert_expr(:unary_#{op}), :#{op}), start_pos)
|
251
316
|
end
|
252
317
|
RUBY
|
@@ -273,6 +338,7 @@ RUBY
|
|
273
338
|
return list e, start_pos unless @lexer.peek && @lexer.peek.type == :colon
|
274
339
|
|
275
340
|
pair = map_pair(e)
|
341
|
+
@css_variable_warning.warn! if @css_variable_warning
|
276
342
|
map = node(Sass::Script::Tree::MapLiteral.new([pair]), start_pos)
|
277
343
|
while try_tok(:comma)
|
278
344
|
pair = map_pair
|
@@ -308,19 +374,32 @@ RUBY
|
|
308
374
|
end
|
309
375
|
return list unless (e = interpolation)
|
310
376
|
list.elements << e
|
377
|
+
list.source_range.end_pos = list.elements.last.source_range.end_pos
|
311
378
|
end
|
312
379
|
list
|
313
380
|
end
|
314
381
|
|
315
382
|
production :equals, :interpolation, :single_eq
|
316
383
|
|
317
|
-
def try_op_before_interp(op, prev = nil)
|
384
|
+
def try_op_before_interp(op, prev = nil, after_interp = false)
|
318
385
|
return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
|
386
|
+
unary = !prev && !after_interp
|
319
387
|
wb = @lexer.whitespace?(op)
|
320
388
|
str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
|
321
389
|
op.source_range)
|
390
|
+
|
391
|
+
deprecation =
|
392
|
+
case op.type
|
393
|
+
when :comma; :potential
|
394
|
+
when :div, :single_eq; :none
|
395
|
+
when :plus; unary ? :none : :immediate
|
396
|
+
when :minus; @lexer.whitespace?(@lexer.peek) ? :immediate : :none
|
397
|
+
else; :immediate
|
398
|
+
end
|
399
|
+
|
322
400
|
interp = node(
|
323
|
-
Script::Tree::Interpolation.new(
|
401
|
+
Script::Tree::Interpolation.new(
|
402
|
+
prev, str, nil, wb, false, :originally_text => true, :deprecation => deprecation),
|
324
403
|
(prev || str).source_range.start_pos)
|
325
404
|
interpolation(interp)
|
326
405
|
end
|
@@ -329,15 +408,25 @@ RUBY
|
|
329
408
|
return unless @lexer.after_interpolation?
|
330
409
|
op = try_toks(*ops)
|
331
410
|
return unless op
|
332
|
-
interp = try_op_before_interp(op, prev)
|
411
|
+
interp = try_op_before_interp(op, prev, :after_interp)
|
333
412
|
return interp if interp
|
334
413
|
|
335
414
|
wa = @lexer.whitespace?
|
336
415
|
str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
|
337
416
|
op.source_range)
|
338
417
|
str.line = @lexer.line
|
418
|
+
|
419
|
+
deprecation =
|
420
|
+
case op.type
|
421
|
+
when :comma; :potential
|
422
|
+
when :div, :single_eq; :none
|
423
|
+
when :minus; @lexer.whitespace?(op) ? :immediate : :none
|
424
|
+
else; :immediate
|
425
|
+
end
|
339
426
|
interp = node(
|
340
|
-
Script::Tree::Interpolation.new(
|
427
|
+
Script::Tree::Interpolation.new(
|
428
|
+
prev, str, assert_expr(name), false, wa,
|
429
|
+
:originally_text => true, :deprecation => deprecation),
|
341
430
|
(prev || str).source_range.start_pos)
|
342
431
|
interp
|
343
432
|
end
|
@@ -346,16 +435,60 @@ RUBY
|
|
346
435
|
e = first
|
347
436
|
while (interp = try_tok(:begin_interpolation))
|
348
437
|
wb = @lexer.whitespace?(interp)
|
349
|
-
|
438
|
+
char_before = @lexer.char(interp.pos - 1)
|
439
|
+
mid = without_css_variable_warning {assert_expr :expr}
|
350
440
|
assert_tok :end_interpolation
|
351
441
|
wa = @lexer.whitespace?
|
442
|
+
char_after = @lexer.char
|
443
|
+
|
444
|
+
after = space
|
445
|
+
before_deprecation = e.is_a?(Script::Tree::Interpolation) ? e.deprecation : :none
|
446
|
+
after_deprecation = after.is_a?(Script::Tree::Interpolation) ? after.deprecation : :none
|
447
|
+
|
448
|
+
deprecation =
|
449
|
+
if before_deprecation == :immediate || after_deprecation == :immediate ||
|
450
|
+
# Warn for #{foo}$var and #{foo}(1) but not #{$foo}1.
|
451
|
+
(after && !wa && char_after =~ /[$(]/) ||
|
452
|
+
# Warn for $var#{foo} and (a)#{foo} but not a#{foo}.
|
453
|
+
(e && !wb && is_unsafe_before?(e, char_before))
|
454
|
+
:immediate
|
455
|
+
else
|
456
|
+
:potential
|
457
|
+
end
|
458
|
+
|
352
459
|
e = node(
|
353
|
-
Script::Tree::Interpolation.new(e, mid,
|
354
|
-
(e ||
|
460
|
+
Script::Tree::Interpolation.new(e, mid, after, wb, wa, :deprecation => deprecation),
|
461
|
+
(e || interp).source_range.start_pos)
|
355
462
|
end
|
356
463
|
e
|
357
464
|
end
|
358
465
|
|
466
|
+
# Returns whether `expr` is unsafe to include before an interpolation.
|
467
|
+
#
|
468
|
+
# @param expr [Node] The expression to check.
|
469
|
+
# @param char_before [String] The character immediately before the
|
470
|
+
# interpolation being checked (and presumably the last character of
|
471
|
+
# `expr`).
|
472
|
+
# @return [Boolean]
|
473
|
+
def is_unsafe_before?(expr, char_before)
|
474
|
+
return char_before == ')' if is_safe_value?(expr)
|
475
|
+
|
476
|
+
# Otherwise, it's only safe if it was another interpolation.
|
477
|
+
!expr.is_a?(Script::Tree::Interpolation)
|
478
|
+
end
|
479
|
+
|
480
|
+
# Returns whether `expr` is safe as the value immediately before an
|
481
|
+
# interpolation.
|
482
|
+
#
|
483
|
+
# It's safe as long as the previous expression is an identifier or number,
|
484
|
+
# or a list whose last element is also safe.
|
485
|
+
def is_safe_value?(expr)
|
486
|
+
return is_safe_value?(expr.elements.last) if expr.is_a?(Script::Tree::ListLiteral)
|
487
|
+
return false unless expr.is_a?(Script::Tree::Literal)
|
488
|
+
expr.value.is_a?(Script::Value::Number) ||
|
489
|
+
(expr.value.is_a?(Script::Value::String) && expr.value.type == :identifier)
|
490
|
+
end
|
491
|
+
|
359
492
|
def space
|
360
493
|
start_pos = source_position
|
361
494
|
e = or_expr
|
@@ -497,30 +630,33 @@ RUBY
|
|
497
630
|
first = try_tok(:special_fun)
|
498
631
|
return paren unless first
|
499
632
|
str = literal_node(first.value, first.source_range)
|
500
|
-
return str unless try_tok(:
|
501
|
-
mid =
|
633
|
+
return str unless try_tok(:string_interpolation)
|
634
|
+
mid = without_css_variable_warning {assert_expr :expr}
|
635
|
+
assert_tok :end_interpolation
|
502
636
|
last = assert_expr(:special_fun)
|
503
|
-
node(
|
504
|
-
|
637
|
+
node(
|
638
|
+
Tree::Interpolation.new(str, mid, last, false, false),
|
639
|
+
first.source_range.start_pos)
|
505
640
|
end
|
506
641
|
|
507
642
|
def paren
|
508
643
|
return variable unless try_tok(:lparen)
|
509
|
-
was_in_parens = @in_parens
|
510
|
-
@in_parens = true
|
511
644
|
start_pos = source_position
|
512
645
|
e = map
|
646
|
+
e.force_division! if e
|
513
647
|
end_pos = source_position
|
514
648
|
assert_tok(:rparen)
|
515
|
-
|
516
|
-
|
517
|
-
|
649
|
+
|
650
|
+
@css_variable_warning.warn! if @css_variable_warning
|
651
|
+
e || node(Sass::Script::Tree::ListLiteral.new([], nil), start_pos, end_pos)
|
518
652
|
end
|
519
653
|
|
520
654
|
def variable
|
521
655
|
start_pos = source_position
|
522
656
|
c = try_tok(:const)
|
523
657
|
return string unless c
|
658
|
+
|
659
|
+
@css_variable_warning.warn! if @css_variable_warning
|
524
660
|
node(Tree::Variable.new(*c.value), start_pos)
|
525
661
|
end
|
526
662
|
|
@@ -528,8 +664,8 @@ RUBY
|
|
528
664
|
first = try_tok(:string)
|
529
665
|
return number unless first
|
530
666
|
str = literal_node(first.value, first.source_range)
|
531
|
-
return str unless try_tok(:
|
532
|
-
mid = assert_expr :expr
|
667
|
+
return str unless try_tok(:string_interpolation)
|
668
|
+
mid = without_css_variable_warning {assert_expr :expr}
|
533
669
|
assert_tok :end_interpolation
|
534
670
|
last = assert_expr(:string)
|
535
671
|
node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
|
@@ -539,13 +675,15 @@ RUBY
|
|
539
675
|
tok = try_tok(:number)
|
540
676
|
return selector unless tok
|
541
677
|
num = tok.value
|
542
|
-
num.
|
678
|
+
num.options = @options
|
679
|
+
num.original = num.to_s
|
543
680
|
literal_node(num, tok.source_range.start_pos)
|
544
681
|
end
|
545
682
|
|
546
683
|
def selector
|
547
684
|
tok = try_tok(:selector)
|
548
685
|
return literal unless tok
|
686
|
+
@css_variable_warning.warn! if @css_variable_warning
|
549
687
|
node(tok.value, tok.source_range.start_pos)
|
550
688
|
end
|
551
689
|
|
@@ -597,8 +735,14 @@ RUBY
|
|
597
735
|
end
|
598
736
|
|
599
737
|
def assert_done
|
600
|
-
|
601
|
-
|
738
|
+
if @allow_extra_text
|
739
|
+
# If extra text is allowed, just rewind the lexer so that the
|
740
|
+
# StringScanner is pointing to the end of the parsed text.
|
741
|
+
@lexer.unpeek!
|
742
|
+
else
|
743
|
+
return if @lexer.done?
|
744
|
+
@lexer.expected!(EXPR_NAMES[:default])
|
745
|
+
end
|
602
746
|
end
|
603
747
|
|
604
748
|
# @overload node(value, source_range)
|
@@ -627,11 +771,62 @@ RUBY
|
|
627
771
|
range(source_range_or_start_pos, end_pos)
|
628
772
|
end
|
629
773
|
|
774
|
+
node.css_variable_warning = @css_variable_warning
|
630
775
|
node.line = source_range.start_pos.line
|
631
776
|
node.source_range = source_range
|
632
777
|
node.filename = @options[:filename]
|
633
778
|
node
|
634
779
|
end
|
780
|
+
|
781
|
+
# Runs the given block without CSS variable warnings enabled.
|
782
|
+
#
|
783
|
+
# CSS warnings don't apply within interpolation, so this is used to
|
784
|
+
# disable them.
|
785
|
+
#
|
786
|
+
# @yield []
|
787
|
+
def without_css_variable_warning
|
788
|
+
old_css_variable_warning = @css_variable_warning
|
789
|
+
@css_variable_warning = nil
|
790
|
+
yield
|
791
|
+
ensure
|
792
|
+
@css_variable_warning = old_css_variable_warning
|
793
|
+
end
|
794
|
+
|
795
|
+
# Checks a script node for any immediately-deprecated interpolations, and
|
796
|
+
# emits warnings for them.
|
797
|
+
#
|
798
|
+
# @param node [Sass::Script::Tree::Node]
|
799
|
+
def check_for_interpolation(node)
|
800
|
+
nodes = [node]
|
801
|
+
until nodes.empty?
|
802
|
+
node = nodes.pop
|
803
|
+
unless node.is_a?(Sass::Script::Tree::Interpolation) &&
|
804
|
+
node.deprecation == :immediate
|
805
|
+
nodes.concat node.children
|
806
|
+
next
|
807
|
+
end
|
808
|
+
|
809
|
+
interpolation_deprecation(node)
|
810
|
+
end
|
811
|
+
end
|
812
|
+
|
813
|
+
# Emits a deprecation warning for an interpolation node.
|
814
|
+
#
|
815
|
+
# @param node [Sass::Script::Tree::Node]
|
816
|
+
def interpolation_deprecation(interpolation)
|
817
|
+
return if @options[:_convert]
|
818
|
+
location = "on line #{interpolation.line}"
|
819
|
+
location << " of #{interpolation.filename}" if interpolation.filename
|
820
|
+
Sass::Util.sass_warn <<WARNING
|
821
|
+
DEPRECATION WARNING #{location}:
|
822
|
+
\#{} interpolation near operators will be simplified in a future version of Sass.
|
823
|
+
To preserve the current behavior, use quotes:
|
824
|
+
|
825
|
+
#{interpolation.to_quoted_equivalent.to_sass}
|
826
|
+
|
827
|
+
You can use the sass-convert command to automatically fix most cases.
|
828
|
+
WARNING
|
829
|
+
end
|
635
830
|
end
|
636
831
|
end
|
637
832
|
end
|