sass4 4.0.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.
- checksums.yaml +7 -0
- data/.yardopts +13 -0
- data/AGENTS.md +534 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +148 -0
- data/MIT-LICENSE +20 -0
- data/README.md +242 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/sass +13 -0
- data/bin/sass-convert +12 -0
- data/bin/scss +13 -0
- data/extra/sass-spec-ref.sh +40 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +18 -0
- data/lib/sass/cache_stores/base.rb +88 -0
- data/lib/sass/cache_stores/chain.rb +34 -0
- data/lib/sass/cache_stores/filesystem.rb +60 -0
- data/lib/sass/cache_stores/memory.rb +46 -0
- data/lib/sass/cache_stores/null.rb +25 -0
- data/lib/sass/cache_stores.rb +15 -0
- data/lib/sass/callbacks.rb +67 -0
- data/lib/sass/css.rb +407 -0
- data/lib/sass/deprecation.rb +55 -0
- data/lib/sass/engine.rb +1236 -0
- data/lib/sass/environment.rb +236 -0
- data/lib/sass/error.rb +198 -0
- data/lib/sass/exec/base.rb +188 -0
- data/lib/sass/exec/sass_convert.rb +283 -0
- data/lib/sass/exec/sass_scss.rb +436 -0
- data/lib/sass/exec.rb +9 -0
- data/lib/sass/features.rb +48 -0
- data/lib/sass/importers/base.rb +182 -0
- data/lib/sass/importers/deprecated_path.rb +51 -0
- data/lib/sass/importers/filesystem.rb +221 -0
- data/lib/sass/importers.rb +23 -0
- data/lib/sass/logger/base.rb +47 -0
- data/lib/sass/logger/delayed.rb +50 -0
- data/lib/sass/logger/log_level.rb +45 -0
- data/lib/sass/logger.rb +17 -0
- data/lib/sass/media.rb +210 -0
- data/lib/sass/plugin/compiler.rb +552 -0
- data/lib/sass/plugin/configuration.rb +134 -0
- data/lib/sass/plugin/generic.rb +15 -0
- data/lib/sass/plugin/merb.rb +48 -0
- data/lib/sass/plugin/rack.rb +60 -0
- data/lib/sass/plugin/rails.rb +47 -0
- data/lib/sass/plugin/staleness_checker.rb +199 -0
- data/lib/sass/plugin.rb +134 -0
- data/lib/sass/railtie.rb +10 -0
- data/lib/sass/repl.rb +57 -0
- data/lib/sass/root.rb +7 -0
- data/lib/sass/script/css_lexer.rb +33 -0
- data/lib/sass/script/css_parser.rb +36 -0
- data/lib/sass/script/functions.rb +3103 -0
- data/lib/sass/script/lexer.rb +518 -0
- data/lib/sass/script/parser.rb +1164 -0
- data/lib/sass/script/tree/funcall.rb +314 -0
- data/lib/sass/script/tree/interpolation.rb +220 -0
- data/lib/sass/script/tree/list_literal.rb +119 -0
- data/lib/sass/script/tree/literal.rb +49 -0
- data/lib/sass/script/tree/map_literal.rb +64 -0
- data/lib/sass/script/tree/node.rb +119 -0
- data/lib/sass/script/tree/operation.rb +149 -0
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/tree/string_interpolation.rb +125 -0
- data/lib/sass/script/tree/unary_operation.rb +69 -0
- data/lib/sass/script/tree/variable.rb +57 -0
- data/lib/sass/script/tree.rb +16 -0
- data/lib/sass/script/value/arg_list.rb +36 -0
- data/lib/sass/script/value/base.rb +258 -0
- data/lib/sass/script/value/bool.rb +35 -0
- data/lib/sass/script/value/callable.rb +25 -0
- data/lib/sass/script/value/color.rb +704 -0
- data/lib/sass/script/value/function.rb +19 -0
- data/lib/sass/script/value/helpers.rb +298 -0
- data/lib/sass/script/value/list.rb +135 -0
- data/lib/sass/script/value/map.rb +70 -0
- data/lib/sass/script/value/null.rb +44 -0
- data/lib/sass/script/value/number.rb +564 -0
- data/lib/sass/script/value/string.rb +138 -0
- data/lib/sass/script/value.rb +13 -0
- data/lib/sass/script.rb +66 -0
- data/lib/sass/scss/css_parser.rb +61 -0
- data/lib/sass/scss/parser.rb +1343 -0
- data/lib/sass/scss/rx.rb +134 -0
- data/lib/sass/scss/static_parser.rb +351 -0
- data/lib/sass/scss.rb +14 -0
- data/lib/sass/selector/abstract_sequence.rb +112 -0
- data/lib/sass/selector/comma_sequence.rb +195 -0
- data/lib/sass/selector/pseudo.rb +291 -0
- data/lib/sass/selector/sequence.rb +661 -0
- data/lib/sass/selector/simple.rb +124 -0
- data/lib/sass/selector/simple_sequence.rb +348 -0
- data/lib/sass/selector.rb +327 -0
- data/lib/sass/shared.rb +76 -0
- data/lib/sass/source/map.rb +209 -0
- data/lib/sass/source/position.rb +39 -0
- data/lib/sass/source/range.rb +41 -0
- data/lib/sass/stack.rb +140 -0
- data/lib/sass/supports.rb +225 -0
- data/lib/sass/tree/at_root_node.rb +83 -0
- data/lib/sass/tree/charset_node.rb +22 -0
- data/lib/sass/tree/comment_node.rb +82 -0
- data/lib/sass/tree/content_node.rb +9 -0
- data/lib/sass/tree/css_import_node.rb +68 -0
- data/lib/sass/tree/debug_node.rb +18 -0
- data/lib/sass/tree/directive_node.rb +59 -0
- data/lib/sass/tree/each_node.rb +24 -0
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/extend_node.rb +43 -0
- data/lib/sass/tree/for_node.rb +36 -0
- data/lib/sass/tree/function_node.rb +44 -0
- data/lib/sass/tree/if_node.rb +52 -0
- data/lib/sass/tree/import_node.rb +75 -0
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/media_node.rb +48 -0
- data/lib/sass/tree/mixin_def_node.rb +38 -0
- data/lib/sass/tree/mixin_node.rb +52 -0
- data/lib/sass/tree/node.rb +240 -0
- data/lib/sass/tree/prop_node.rb +162 -0
- data/lib/sass/tree/return_node.rb +19 -0
- data/lib/sass/tree/root_node.rb +44 -0
- data/lib/sass/tree/rule_node.rb +153 -0
- data/lib/sass/tree/supports_node.rb +38 -0
- data/lib/sass/tree/trace_node.rb +33 -0
- data/lib/sass/tree/variable_node.rb +36 -0
- data/lib/sass/tree/visitors/base.rb +72 -0
- data/lib/sass/tree/visitors/check_nesting.rb +173 -0
- data/lib/sass/tree/visitors/convert.rb +350 -0
- data/lib/sass/tree/visitors/cssize.rb +362 -0
- data/lib/sass/tree/visitors/deep_copy.rb +107 -0
- data/lib/sass/tree/visitors/extend.rb +64 -0
- data/lib/sass/tree/visitors/perform.rb +572 -0
- data/lib/sass/tree/visitors/set_options.rb +139 -0
- data/lib/sass/tree/visitors/to_css.rb +440 -0
- data/lib/sass/tree/warn_node.rb +18 -0
- data/lib/sass/tree/while_node.rb +18 -0
- data/lib/sass/util/multibyte_string_scanner.rb +151 -0
- data/lib/sass/util/normalized_map.rb +122 -0
- data/lib/sass/util/subset_map.rb +109 -0
- data/lib/sass/util/test.rb +9 -0
- data/lib/sass/util.rb +1137 -0
- data/lib/sass/version.rb +120 -0
- data/lib/sass.rb +102 -0
- data/rails/init.rb +1 -0
- metadata +283 -0
@@ -0,0 +1,518 @@
|
|
1
|
+
require 'sass/scss/rx'
|
2
|
+
|
3
|
+
module Sass
|
4
|
+
module Script
|
5
|
+
# The lexical analyzer for SassScript.
|
6
|
+
# It takes a raw string and converts it to individual tokens
|
7
|
+
# that are easier to parse.
|
8
|
+
class Lexer
|
9
|
+
include Sass::SCSS::RX
|
10
|
+
|
11
|
+
# A struct containing information about an individual token.
|
12
|
+
#
|
13
|
+
# `type`: \[`Symbol`\]
|
14
|
+
# : The type of token.
|
15
|
+
#
|
16
|
+
# `value`: \[`Object`\]
|
17
|
+
# : The Ruby object corresponding to the value of the token.
|
18
|
+
#
|
19
|
+
# `source_range`: \[`Sass::Source::Range`\]
|
20
|
+
# : The range in the source file in which the token appeared.
|
21
|
+
#
|
22
|
+
# `pos`: \[`Integer`\]
|
23
|
+
# : The scanner position at which the SassScript token appeared.
|
24
|
+
Token = Struct.new(:type, :value, :source_range, :pos)
|
25
|
+
|
26
|
+
# The line number of the lexer's current position.
|
27
|
+
#
|
28
|
+
# @return [Integer]
|
29
|
+
def line
|
30
|
+
return @line unless @tok
|
31
|
+
@tok.source_range.start_pos.line
|
32
|
+
end
|
33
|
+
|
34
|
+
# The number of bytes into the current line
|
35
|
+
# of the lexer's current position (1-based).
|
36
|
+
#
|
37
|
+
# @return [Integer]
|
38
|
+
def offset
|
39
|
+
return @offset unless @tok
|
40
|
+
@tok.source_range.start_pos.offset
|
41
|
+
end
|
42
|
+
|
43
|
+
# A hash from operator strings to the corresponding token types.
|
44
|
+
OPERATORS = {
|
45
|
+
'+' => :plus,
|
46
|
+
'-' => :minus,
|
47
|
+
'*' => :times,
|
48
|
+
'/' => :div,
|
49
|
+
'%' => :mod,
|
50
|
+
'=' => :single_eq,
|
51
|
+
':' => :colon,
|
52
|
+
'(' => :lparen,
|
53
|
+
')' => :rparen,
|
54
|
+
'[' => :lsquare,
|
55
|
+
']' => :rsquare,
|
56
|
+
',' => :comma,
|
57
|
+
'and' => :and,
|
58
|
+
'or' => :or,
|
59
|
+
'not' => :not,
|
60
|
+
'==' => :eq,
|
61
|
+
'!=' => :neq,
|
62
|
+
'>=' => :gte,
|
63
|
+
'<=' => :lte,
|
64
|
+
'>' => :gt,
|
65
|
+
'<' => :lt,
|
66
|
+
'#{' => :begin_interpolation,
|
67
|
+
'}' => :end_interpolation,
|
68
|
+
';' => :semicolon,
|
69
|
+
'{' => :lcurly,
|
70
|
+
'...' => :splat,
|
71
|
+
}
|
72
|
+
|
73
|
+
OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
|
74
|
+
|
75
|
+
TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge(
|
76
|
+
:const => "variable (e.g. $foo)",
|
77
|
+
:ident => "identifier (e.g. middle)")
|
78
|
+
|
79
|
+
# A list of operator strings ordered with longer names first
|
80
|
+
# so that `>` and `<` don't clobber `>=` and `<=`.
|
81
|
+
OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
|
82
|
+
|
83
|
+
# A sub-list of {OP_NAMES} that only includes operators
|
84
|
+
# with identifier names.
|
85
|
+
IDENT_OP_NAMES = OP_NAMES.select {|k, _v| k =~ /^\w+/}
|
86
|
+
|
87
|
+
PARSEABLE_NUMBER = /(?:(\d*\.\d+)|(\d+))(?:[eE]([+-]?\d+))?(#{UNIT})?/
|
88
|
+
|
89
|
+
# A hash of regular expressions that are used for tokenizing.
|
90
|
+
REGULAR_EXPRESSIONS = {
|
91
|
+
:whitespace => /\s+/,
|
92
|
+
:comment => COMMENT,
|
93
|
+
:single_line_comment => SINGLE_LINE_COMMENT,
|
94
|
+
:variable => /(\$)(#{IDENT})/,
|
95
|
+
:ident => /(#{IDENT})(\()?/,
|
96
|
+
:number => PARSEABLE_NUMBER,
|
97
|
+
:unary_minus_number => /-#{PARSEABLE_NUMBER}/,
|
98
|
+
:color => HEXCOLOR,
|
99
|
+
:id => /##{IDENT}/,
|
100
|
+
:selector => /&/,
|
101
|
+
:ident_op => /(#{Regexp.union(*IDENT_OP_NAMES.map do |s|
|
102
|
+
Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")
|
103
|
+
end)})/,
|
104
|
+
:op => /(#{Regexp.union(*OP_NAMES)})/,
|
105
|
+
}
|
106
|
+
|
107
|
+
class << self
|
108
|
+
private
|
109
|
+
|
110
|
+
def string_re(open, close)
|
111
|
+
/#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# A hash of regular expressions that are used for tokenizing strings.
|
116
|
+
#
|
117
|
+
# The key is a `[Symbol, Boolean]` pair.
|
118
|
+
# The symbol represents which style of quotation to use,
|
119
|
+
# while the boolean represents whether or not the string
|
120
|
+
# is following an interpolated segment.
|
121
|
+
STRING_REGULAR_EXPRESSIONS = {
|
122
|
+
:double => {
|
123
|
+
false => string_re('"', '"'),
|
124
|
+
true => string_re('', '"')
|
125
|
+
},
|
126
|
+
:single => {
|
127
|
+
false => string_re("'", "'"),
|
128
|
+
true => string_re('', "'")
|
129
|
+
},
|
130
|
+
:uri => {
|
131
|
+
false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
|
132
|
+
true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
|
133
|
+
},
|
134
|
+
# Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
|
135
|
+
# non-standard version of http://www.w3.org/TR/css3-conditional/
|
136
|
+
:url_prefix => {
|
137
|
+
false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
|
138
|
+
true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
|
139
|
+
},
|
140
|
+
:domain => {
|
141
|
+
false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
|
142
|
+
true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
# @param str [String, StringScanner] The source text to lex
|
147
|
+
# @param line [Integer] The 1-based line on which the SassScript appears.
|
148
|
+
# Used for error reporting and sourcemap building
|
149
|
+
# @param offset [Integer] The 1-based character (not byte) offset in the line in the source.
|
150
|
+
# Used for error reporting and sourcemap building
|
151
|
+
# @param options [{Symbol => Object}] An options hash;
|
152
|
+
# see {file:SASS_REFERENCE.md#Options the Sass options documentation}
|
153
|
+
def initialize(str, line, offset, options)
|
154
|
+
@scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
|
155
|
+
@line = line
|
156
|
+
@offset = offset
|
157
|
+
@options = options
|
158
|
+
@interpolation_stack = []
|
159
|
+
@prev = nil
|
160
|
+
@tok = nil
|
161
|
+
@next_tok = nil
|
162
|
+
end
|
163
|
+
|
164
|
+
# Moves the lexer forward one token.
|
165
|
+
#
|
166
|
+
# @return [Token] The token that was moved past
|
167
|
+
def next
|
168
|
+
@tok ||= read_token
|
169
|
+
@tok, tok = nil, @tok
|
170
|
+
@prev = tok
|
171
|
+
tok
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns whether or not there's whitespace before the next token.
|
175
|
+
#
|
176
|
+
# @return [Boolean]
|
177
|
+
def whitespace?(tok = @tok)
|
178
|
+
if tok
|
179
|
+
@scanner.string[0...tok.pos] =~ /\s\Z/
|
180
|
+
else
|
181
|
+
@scanner.string[@scanner.pos, 1] =~ /^\s/ ||
|
182
|
+
@scanner.string[@scanner.pos - 1, 1] =~ /\s\Z/
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the given character.
|
187
|
+
#
|
188
|
+
# @return [String]
|
189
|
+
def char(pos = @scanner.pos)
|
190
|
+
@scanner.string[pos, 1]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Consumes and returns single raw character from the input stream.
|
194
|
+
#
|
195
|
+
# @return [String]
|
196
|
+
def next_char
|
197
|
+
unpeek!
|
198
|
+
scan(/./)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns the next token without moving the lexer forward.
|
202
|
+
#
|
203
|
+
# @return [Token] The next token
|
204
|
+
def peek
|
205
|
+
@tok ||= read_token
|
206
|
+
end
|
207
|
+
|
208
|
+
# Rewinds the underlying StringScanner
|
209
|
+
# to before the token returned by \{#peek}.
|
210
|
+
def unpeek!
|
211
|
+
raise "[BUG] Can't unpeek before a queued token!" if @next_tok
|
212
|
+
return unless @tok
|
213
|
+
@scanner.pos = @tok.pos
|
214
|
+
@line = @tok.source_range.start_pos.line
|
215
|
+
@offset = @tok.source_range.start_pos.offset
|
216
|
+
end
|
217
|
+
|
218
|
+
# @return [Boolean] Whether or not there's more source text to lex.
|
219
|
+
def done?
|
220
|
+
return if @next_tok
|
221
|
+
whitespace unless after_interpolation? && !@interpolation_stack.empty?
|
222
|
+
@scanner.eos? && @tok.nil?
|
223
|
+
end
|
224
|
+
|
225
|
+
# @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
|
226
|
+
def after_interpolation?
|
227
|
+
@prev && @prev.type == :end_interpolation
|
228
|
+
end
|
229
|
+
|
230
|
+
# Raise an error to the effect that `name` was expected in the input stream
|
231
|
+
# and wasn't found.
|
232
|
+
#
|
233
|
+
# This calls \{#unpeek!} to rewind the scanner to immediately after
|
234
|
+
# the last returned token.
|
235
|
+
#
|
236
|
+
# @param name [String] The name of the entity that was expected but not found
|
237
|
+
# @raise [Sass::SyntaxError]
|
238
|
+
def expected!(name)
|
239
|
+
unpeek!
|
240
|
+
Sass::SCSS::Parser.expected(@scanner, name, @line)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Records all non-comment text the lexer consumes within the block
|
244
|
+
# and returns it as a string.
|
245
|
+
#
|
246
|
+
# @yield A block in which text is recorded
|
247
|
+
# @return [String]
|
248
|
+
def str
|
249
|
+
old_pos = @tok ? @tok.pos : @scanner.pos
|
250
|
+
yield
|
251
|
+
new_pos = @tok ? @tok.pos : @scanner.pos
|
252
|
+
@scanner.string[old_pos...new_pos]
|
253
|
+
end
|
254
|
+
|
255
|
+
# Runs a block, and rewinds the state of the lexer to the beginning of the
|
256
|
+
# block if it returns `nil` or `false`.
|
257
|
+
def try
|
258
|
+
old_pos = @scanner.pos
|
259
|
+
old_line = @line
|
260
|
+
old_offset = @offset
|
261
|
+
old_interpolation_stack = @interpolation_stack.dup
|
262
|
+
old_prev = @prev
|
263
|
+
old_tok = @tok
|
264
|
+
old_next_tok = @next_tok
|
265
|
+
|
266
|
+
result = yield
|
267
|
+
return result if result
|
268
|
+
|
269
|
+
@scanner.pos = old_pos
|
270
|
+
@line = old_line
|
271
|
+
@offset = old_offset
|
272
|
+
@interpolation_stack = old_interpolation_stack
|
273
|
+
@prev = old_prev
|
274
|
+
@tok = old_tok
|
275
|
+
@next_tok = old_next_tok
|
276
|
+
nil
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
def read_token
|
282
|
+
if (tok = @next_tok)
|
283
|
+
@next_tok = nil
|
284
|
+
return tok
|
285
|
+
end
|
286
|
+
|
287
|
+
return if done?
|
288
|
+
start_pos = source_position
|
289
|
+
value = token
|
290
|
+
return unless value
|
291
|
+
type, val = value
|
292
|
+
Token.new(type, val, range(start_pos), @scanner.pos - @scanner.matched_size)
|
293
|
+
end
|
294
|
+
|
295
|
+
def whitespace
|
296
|
+
nil while scan(REGULAR_EXPRESSIONS[:whitespace]) ||
|
297
|
+
scan(REGULAR_EXPRESSIONS[:comment]) ||
|
298
|
+
scan(REGULAR_EXPRESSIONS[:single_line_comment])
|
299
|
+
end
|
300
|
+
|
301
|
+
def token
|
302
|
+
if after_interpolation?
|
303
|
+
interp_type, interp_value = @interpolation_stack.pop
|
304
|
+
if interp_type == :special_fun
|
305
|
+
return special_fun_body(interp_value)
|
306
|
+
elsif interp_type.nil?
|
307
|
+
if @scanner.string[@scanner.pos - 1] == '}' && scan(REGULAR_EXPRESSIONS[:ident])
|
308
|
+
return [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1], start: false)]
|
309
|
+
end
|
310
|
+
else
|
311
|
+
raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
|
312
|
+
return string(interp_value, true)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
variable || string(:double, false) || string(:single, false) || number || id || color ||
|
317
|
+
selector || string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val ||
|
318
|
+
ident_op || ident || op
|
319
|
+
end
|
320
|
+
|
321
|
+
def variable
|
322
|
+
_variable(REGULAR_EXPRESSIONS[:variable])
|
323
|
+
end
|
324
|
+
|
325
|
+
def _variable(rx)
|
326
|
+
return unless scan(rx)
|
327
|
+
[:const, Sass::Util.normalize_ident_escapes(@scanner[2])]
|
328
|
+
end
|
329
|
+
|
330
|
+
def ident
|
331
|
+
return unless scan(REGULAR_EXPRESSIONS[:ident])
|
332
|
+
[@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1])]
|
333
|
+
end
|
334
|
+
|
335
|
+
def string(re, open)
|
336
|
+
line, offset = @line, @offset
|
337
|
+
return unless scan(STRING_REGULAR_EXPRESSIONS[re][open])
|
338
|
+
if @scanner[0] =~ /([^\\]|^)\n/
|
339
|
+
filename = @options[:filename]
|
340
|
+
Sass::Util.sass_warn <<MESSAGE
|
341
|
+
DEPRECATION WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
|
342
|
+
Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
|
343
|
+
To include a newline in a string, use "\\a" or "\\a " as in CSS.
|
344
|
+
MESSAGE
|
345
|
+
end
|
346
|
+
|
347
|
+
if @scanner[2] == '#{' # '
|
348
|
+
@interpolation_stack << [:string, re]
|
349
|
+
start_pos = Sass::Source::Position.new(@line, @offset - 2)
|
350
|
+
@next_tok = Token.new(:string_interpolation, range(start_pos), @scanner.pos - 2)
|
351
|
+
end
|
352
|
+
str =
|
353
|
+
if re == :uri
|
354
|
+
url = "#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}"
|
355
|
+
Script::Value::String.new(url)
|
356
|
+
else
|
357
|
+
Script::Value::String.new(Sass::Script::Value::String.value(@scanner[1]), :string)
|
358
|
+
end
|
359
|
+
[:string, str]
|
360
|
+
end
|
361
|
+
|
362
|
+
def number
|
363
|
+
# Handling unary minus is complicated by the fact that whitespace is an
|
364
|
+
# operator in SassScript. We want "1-2" to be parsed as "1 - 2", but we
|
365
|
+
# want "1 -2" to be parsed as "1 (-2)". To accomplish this, we only
|
366
|
+
# parse a unary minus as part of a number literal if there's whitespace
|
367
|
+
# before and not after it. Cases like "(-2)" are handled by the unary
|
368
|
+
# minus logic in the parser instead.
|
369
|
+
if @scanner.peek(1) == '-'
|
370
|
+
return if @scanner.pos == 0
|
371
|
+
unary_minus_allowed =
|
372
|
+
case @scanner.string[@scanner.pos - 1, 1]
|
373
|
+
when /\s/; true
|
374
|
+
when '/'; @scanner.pos != 1 && @scanner.string[@scanner.pos - 2, 1] == '*'
|
375
|
+
else; false
|
376
|
+
end
|
377
|
+
|
378
|
+
return unless unary_minus_allowed
|
379
|
+
return unless scan(REGULAR_EXPRESSIONS[:unary_minus_number])
|
380
|
+
minus = true
|
381
|
+
else
|
382
|
+
return unless scan(REGULAR_EXPRESSIONS[:number])
|
383
|
+
minus = false
|
384
|
+
end
|
385
|
+
|
386
|
+
value = (@scanner[1] ? @scanner[1].to_f : @scanner[2].to_i) * (minus ? -1 : 1)
|
387
|
+
value *= 10**@scanner[3].to_i if @scanner[3]
|
388
|
+
units = @scanner[4]
|
389
|
+
units = Sass::Util::normalize_ident_escapes(units) if units
|
390
|
+
script_number = Script::Value::Number.new(value, Array(units))
|
391
|
+
[:number, script_number]
|
392
|
+
end
|
393
|
+
|
394
|
+
def id
|
395
|
+
# Colors and ids are tough to tell apart, because they overlap but
|
396
|
+
# neither is a superset of the other. "#xyz" is an id but not a color,
|
397
|
+
# "#000" is a color but not an id, "#abc" is both, and "#0" is neither.
|
398
|
+
# We need to handle all these cases correctly.
|
399
|
+
#
|
400
|
+
# To do so, we first try to parse something as an id. If this works and
|
401
|
+
# the id is also a valid color, we return the color. Otherwise, we
|
402
|
+
# return the id. If it didn't parse as an id, we then try to parse it as
|
403
|
+
# a color. If *this* works, we return the color, and if it doesn't we
|
404
|
+
# give up and throw an error.
|
405
|
+
#
|
406
|
+
# IDs in properties are used in the Basic User Interface Module
|
407
|
+
# (http://www.w3.org/TR/css3-ui/).
|
408
|
+
return unless scan(REGULAR_EXPRESSIONS[:id])
|
409
|
+
if @scanner[0] =~ /^\#[0-9a-fA-F]+$/ &&
|
410
|
+
(@scanner[0].length == 4 || @scanner[0].length == 5 ||
|
411
|
+
@scanner[0].length == 7 || @scanner[0].length == 9)
|
412
|
+
return [:color, Script::Value::Color.from_hex(@scanner[0])]
|
413
|
+
end
|
414
|
+
[:ident, Sass::Util.normalize_ident_escapes(@scanner[0])]
|
415
|
+
end
|
416
|
+
|
417
|
+
def color
|
418
|
+
return unless @scanner.match?(REGULAR_EXPRESSIONS[:color])
|
419
|
+
unless @scanner[0].length == 4 || @scanner[0].length == 5 ||
|
420
|
+
@scanner[0].length == 7 || @scanner[0].length == 9
|
421
|
+
return
|
422
|
+
end
|
423
|
+
script_color = Script::Value::Color.from_hex(scan(REGULAR_EXPRESSIONS[:color]))
|
424
|
+
[:color, script_color]
|
425
|
+
end
|
426
|
+
|
427
|
+
def selector
|
428
|
+
start_pos = source_position
|
429
|
+
return unless scan(REGULAR_EXPRESSIONS[:selector])
|
430
|
+
|
431
|
+
if @scanner.peek(1) == '&'
|
432
|
+
filename = @options[:filename]
|
433
|
+
Sass::Util.sass_warn <<MESSAGE
|
434
|
+
WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
|
435
|
+
In Sass, "&&" means two copies of the parent selector. You probably want to use "and" instead.
|
436
|
+
MESSAGE
|
437
|
+
end
|
438
|
+
|
439
|
+
script_selector = Script::Tree::Selector.new
|
440
|
+
script_selector.source_range = range(start_pos)
|
441
|
+
[:selector, script_selector]
|
442
|
+
end
|
443
|
+
|
444
|
+
def special_fun
|
445
|
+
prefix = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
|
446
|
+
return unless prefix
|
447
|
+
special_fun_body(1, prefix)
|
448
|
+
end
|
449
|
+
|
450
|
+
def special_fun_body(parens, prefix = nil)
|
451
|
+
str = prefix || ''
|
452
|
+
while (scanned = scan(/.*?([()]|\#\{)/m))
|
453
|
+
str << scanned
|
454
|
+
if scanned[-1] == ?(
|
455
|
+
parens += 1
|
456
|
+
next
|
457
|
+
elsif scanned[-1] == ?)
|
458
|
+
parens -= 1
|
459
|
+
next unless parens == 0
|
460
|
+
else
|
461
|
+
raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
|
462
|
+
str.slice!(-2..-1)
|
463
|
+
@interpolation_stack << [:special_fun, parens]
|
464
|
+
start_pos = Sass::Source::Position.new(@line, @offset - 2)
|
465
|
+
@next_tok = Token.new(:string_interpolation, range(start_pos), @scanner.pos - 2)
|
466
|
+
end
|
467
|
+
|
468
|
+
return [:special_fun, Sass::Script::Value::String.new(str)]
|
469
|
+
end
|
470
|
+
|
471
|
+
scan(/.*/)
|
472
|
+
expected!('")"')
|
473
|
+
end
|
474
|
+
|
475
|
+
def special_val
|
476
|
+
return unless scan(/!#{W}important/i)
|
477
|
+
[:string, Script::Value::String.new("!important")]
|
478
|
+
end
|
479
|
+
|
480
|
+
def ident_op
|
481
|
+
op = scan(REGULAR_EXPRESSIONS[:ident_op])
|
482
|
+
return unless op
|
483
|
+
[OPERATORS[op]]
|
484
|
+
end
|
485
|
+
|
486
|
+
def op
|
487
|
+
op = scan(REGULAR_EXPRESSIONS[:op])
|
488
|
+
return unless op
|
489
|
+
name = OPERATORS[op]
|
490
|
+
@interpolation_stack << nil if name == :begin_interpolation
|
491
|
+
[name]
|
492
|
+
end
|
493
|
+
|
494
|
+
def raw(rx)
|
495
|
+
val = scan(rx)
|
496
|
+
return unless val
|
497
|
+
[:raw, val]
|
498
|
+
end
|
499
|
+
|
500
|
+
def scan(re)
|
501
|
+
str = @scanner.scan(re)
|
502
|
+
return unless str
|
503
|
+
c = str.count("\n")
|
504
|
+
@line += c
|
505
|
+
@offset = (c == 0 ? @offset + str.size : str.size - str.rindex("\n"))
|
506
|
+
str
|
507
|
+
end
|
508
|
+
|
509
|
+
def range(start_pos, end_pos = source_position)
|
510
|
+
Sass::Source::Range.new(start_pos, end_pos, @options[:filename], @options[:importer])
|
511
|
+
end
|
512
|
+
|
513
|
+
def source_position
|
514
|
+
Sass::Source::Position.new(@line, @offset)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|