sass 3.3.0.alpha.16 → 3.3.0.alpha.50
Sign up to get free protection for your applications and to get access to all the features.
- data/REVISION +1 -1
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/lib/sass/engine.rb +135 -31
- data/lib/sass/exec.rb +38 -9
- data/lib/sass/plugin/compiler.rb +26 -13
- data/lib/sass/script/lexer.rb +40 -18
- data/lib/sass/script/node.rb +10 -0
- data/lib/sass/script/parser.rb +50 -18
- data/lib/sass/scss/parser.rb +107 -68
- data/lib/sass/scss/static_parser.rb +1 -1
- data/lib/sass/source/map.rb +159 -0
- data/lib/sass/source/position.rb +26 -0
- data/lib/sass/source/range.rb +34 -0
- data/lib/sass/tree/css_import_node.rb +1 -1
- data/lib/sass/tree/node.rb +19 -3
- data/lib/sass/tree/prop_node.rb +11 -1
- data/lib/sass/tree/root_node.rb +19 -3
- data/lib/sass/tree/visitors/perform.rb +9 -1
- data/lib/sass/tree/visitors/to_css.rb +191 -64
- data/lib/sass/util.rb +94 -0
- data/test/sass/source_map_test.rb +837 -0
- data/test/sass/util_test.rb +41 -0
- metadata +9 -4
data/lib/sass/script/lexer.rb
CHANGED
@@ -17,10 +17,10 @@ module Sass
|
|
17
17
|
# : The Ruby object corresponding to the value of the token.
|
18
18
|
#
|
19
19
|
# `line`: \[`Fixnum`\]
|
20
|
-
# : The line of the source file on which the token appears.
|
20
|
+
# : The line of the source file on which the token appears (1-based).
|
21
21
|
#
|
22
22
|
# `offset`: \[`Fixnum`\]
|
23
|
-
# : The number of
|
23
|
+
# : The number of characters into the line the SassScript token appeared (1-based).
|
24
24
|
#
|
25
25
|
# `pos`: \[`Fixnum`\]
|
26
26
|
# : The scanner position at which the SassScript token appeared.
|
@@ -32,7 +32,7 @@ module Sass
|
|
32
32
|
attr_reader :line
|
33
33
|
|
34
34
|
# The number of bytes into the current line
|
35
|
-
# of the lexer's current position.
|
35
|
+
# of the lexer's current position (1-based).
|
36
36
|
#
|
37
37
|
# @return [Fixnum]
|
38
38
|
attr_reader :offset
|
@@ -125,10 +125,10 @@ module Sass
|
|
125
125
|
}
|
126
126
|
|
127
127
|
# @param str [String, StringScanner] The source text to lex
|
128
|
-
# @param line [Fixnum] The line on which the SassScript appears.
|
129
|
-
# Used for error reporting
|
130
|
-
# @param offset [Fixnum] The
|
131
|
-
# Used for error reporting
|
128
|
+
# @param line [Fixnum] The 1-based line on which the SassScript appears.
|
129
|
+
# Used for error reporting and sourcemap building
|
130
|
+
# @param offset [Fixnum] The 1-based character (not byte) offset in the line on which the SassScript appears.
|
131
|
+
# Used for error reporting and sourcemap building
|
132
132
|
# @param options [{Symbol => Object}] An options hash;
|
133
133
|
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
134
134
|
def initialize(str, line, offset, options)
|
@@ -172,7 +172,11 @@ module Sass
|
|
172
172
|
# Rewinds the underlying StringScanner
|
173
173
|
# to before the token returned by \{#peek}.
|
174
174
|
def unpeek!
|
175
|
-
|
175
|
+
if @tok
|
176
|
+
@scanner.pos = @tok.pos
|
177
|
+
@line = @tok.line
|
178
|
+
@offset = @tok.offset
|
179
|
+
end
|
176
180
|
end
|
177
181
|
|
178
182
|
# @return [Boolean] Whether or not there's more source text to lex.
|
@@ -220,8 +224,7 @@ module Sass
|
|
220
224
|
size ||= @scanner.matched_size
|
221
225
|
|
222
226
|
val.line = @line if val.is_a?(Script::Node)
|
223
|
-
Token.new(type, val, @line,
|
224
|
-
current_position - size, @scanner.pos - size)
|
227
|
+
Token.new(type, val, @line, @offset - size, @scanner.pos - size)
|
225
228
|
end
|
226
229
|
|
227
230
|
def whitespace
|
@@ -258,9 +261,11 @@ module Sass
|
|
258
261
|
end
|
259
262
|
|
260
263
|
def string(re, open)
|
264
|
+
start_pos = source_position
|
261
265
|
return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
|
262
266
|
if @scanner[2] == '#{' #'
|
263
267
|
@scanner.pos -= 2 # Don't actually consume the #{
|
268
|
+
@offset -= 2
|
264
269
|
@interpolation_stack << re
|
265
270
|
end
|
266
271
|
str =
|
@@ -269,34 +274,47 @@ module Sass
|
|
269
274
|
else
|
270
275
|
Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
|
271
276
|
end
|
277
|
+
str.source_range = range(start_pos)
|
272
278
|
[:string, str]
|
273
279
|
end
|
274
280
|
|
275
281
|
def number
|
282
|
+
start_pos = source_position
|
276
283
|
return unless scan(REGULAR_EXPRESSIONS[:number])
|
277
284
|
value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
|
278
285
|
value = -value if @scanner[1]
|
279
|
-
|
286
|
+
script_number = Script::Number.new(value, Array(@scanner[4]))
|
287
|
+
script_number.source_range = range(start_pos)
|
288
|
+
[:number, script_number]
|
280
289
|
end
|
281
290
|
|
282
291
|
def color
|
292
|
+
start_pos = source_position
|
283
293
|
return unless s = scan(REGULAR_EXPRESSIONS[:color])
|
284
294
|
raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
|
285
295
|
Colors must have either three or six digits: '#{s}'
|
286
296
|
MESSAGE
|
287
297
|
value = s.scan(/^#(..?)(..?)(..?)$/).first.
|
288
298
|
map {|num| num.ljust(2, num).to_i(16)}
|
289
|
-
|
299
|
+
script_color = Script::Color.new(value)
|
300
|
+
script_color.source_range = range(start_pos)
|
301
|
+
[:color, script_color]
|
290
302
|
end
|
291
303
|
|
292
304
|
def bool
|
305
|
+
start_pos = source_position
|
293
306
|
return unless s = scan(REGULAR_EXPRESSIONS[:bool])
|
294
|
-
|
307
|
+
script_bool = Script::Bool.new(s == 'true')
|
308
|
+
script_bool.source_range = range(start_pos)
|
309
|
+
[:bool, script_bool]
|
295
310
|
end
|
296
311
|
|
297
312
|
def null
|
313
|
+
start_pos = source_position
|
298
314
|
return unless scan(REGULAR_EXPRESSIONS[:null])
|
299
|
-
|
315
|
+
script_null = Script::Null.new
|
316
|
+
script_null.source_range = range(start_pos)
|
317
|
+
[:null, script_null]
|
300
318
|
end
|
301
319
|
|
302
320
|
def special_fun
|
@@ -306,7 +324,7 @@ MESSAGE
|
|
306
324
|
old_line = @line
|
307
325
|
old_offset = @offset
|
308
326
|
@line += c
|
309
|
-
@offset =
|
327
|
+
@offset = c == 0 ? @offset + str2.size : str2[/\n([^\n]*)/, 1].size + 1
|
310
328
|
[:special_fun,
|
311
329
|
Sass::Util.merge_adjacent_strings(
|
312
330
|
[str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
|
@@ -338,12 +356,16 @@ MESSAGE
|
|
338
356
|
return unless str = @scanner.scan(re)
|
339
357
|
c = str.count("\n")
|
340
358
|
@line += c
|
341
|
-
@offset = (c == 0 ? @offset + str.size : str[/\n(
|
359
|
+
@offset = (c == 0 ? @offset + str.size : str[/\n([^\n]*)/, 1].size + 1)
|
342
360
|
str
|
343
361
|
end
|
344
362
|
|
345
|
-
def
|
346
|
-
|
363
|
+
def range(start_pos, end_pos = source_position)
|
364
|
+
Sass::Source::Range.new(start_pos, end_pos, @options[:filename])
|
365
|
+
end
|
366
|
+
|
367
|
+
def source_position
|
368
|
+
Sass::Source::Position.new(@line, @offset)
|
347
369
|
end
|
348
370
|
end
|
349
371
|
end
|
data/lib/sass/script/node.rb
CHANGED
@@ -13,6 +13,16 @@ module Sass::Script
|
|
13
13
|
# @return [Fixnum]
|
14
14
|
attr_accessor :line
|
15
15
|
|
16
|
+
# The source range in the document on which this node appeared.
|
17
|
+
#
|
18
|
+
# @return [Sass::Source::Range]
|
19
|
+
attr_accessor :source_range
|
20
|
+
|
21
|
+
# The file name of the document on which this node appeared.
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
attr_accessor :filename
|
25
|
+
|
16
26
|
# Sets the options hash for this node,
|
17
27
|
# as well as for all child nodes.
|
18
28
|
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
data/lib/sass/script/parser.rb
CHANGED
@@ -12,11 +12,18 @@ module Sass
|
|
12
12
|
@lexer.line
|
13
13
|
end
|
14
14
|
|
15
|
+
# The column number of the parser's current position.
|
16
|
+
#
|
17
|
+
# @return [Fixnum]
|
18
|
+
def offset
|
19
|
+
@lexer.offset
|
20
|
+
end
|
21
|
+
|
15
22
|
# @param str [String, StringScanner] The source text to parse
|
16
23
|
# @param line [Fixnum] The line on which the SassScript appears.
|
17
|
-
# Used for error reporting
|
18
|
-
# @param offset [Fixnum] The
|
19
|
-
# Used for error reporting
|
24
|
+
# Used for error reporting and sourcemap building
|
25
|
+
# @param offset [Fixnum] The character (not byte) offset in the line on which the SassScript appears.
|
26
|
+
# Used for error reporting and sourcemap building
|
20
27
|
# @param options [{Symbol => Object}] An options hash;
|
21
28
|
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
22
29
|
def initialize(str, line, offset, options = {})
|
@@ -32,9 +39,11 @@ module Sass
|
|
32
39
|
# @return [Script::Node] The root node of the parse tree
|
33
40
|
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
|
34
41
|
def parse_interpolated
|
42
|
+
start_pos = source_position
|
35
43
|
expr = assert_expr :expr
|
36
44
|
assert_tok :end_interpolation
|
37
45
|
expr.options = @options
|
46
|
+
expr.source_range = range(start_pos)
|
38
47
|
expr
|
39
48
|
rescue Sass::SyntaxError => e
|
40
49
|
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
@@ -46,9 +55,11 @@ module Sass
|
|
46
55
|
# @return [Script::Node] The root node of the parse tree
|
47
56
|
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
|
48
57
|
def parse
|
58
|
+
start_pos = source_position
|
49
59
|
expr = assert_expr :expr
|
50
60
|
assert_done
|
51
61
|
expr.options = @options
|
62
|
+
expr.source_range = range(start_pos)
|
52
63
|
expr
|
53
64
|
rescue Sass::SyntaxError => e
|
54
65
|
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
@@ -213,9 +224,10 @@ module Sass
|
|
213
224
|
return other_interp
|
214
225
|
end
|
215
226
|
|
216
|
-
|
227
|
+
start_pos = source_position
|
217
228
|
e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
|
218
|
-
e.line = line
|
229
|
+
e.line = start_pos.line
|
230
|
+
e.source_range = range(start_pos)
|
219
231
|
end
|
220
232
|
e
|
221
233
|
end
|
@@ -238,12 +250,24 @@ RUBY
|
|
238
250
|
|
239
251
|
private
|
240
252
|
|
253
|
+
def source_position
|
254
|
+
Sass::Source::Position.new(line, offset)
|
255
|
+
end
|
256
|
+
|
257
|
+
def token_start_position(token)
|
258
|
+
Sass::Source::Position.new(token.line, token.offset)
|
259
|
+
end
|
260
|
+
|
261
|
+
def range(start_pos, end_pos=source_position)
|
262
|
+
Sass::Source::Range.new(start_pos, end_pos, @options[:filename])
|
263
|
+
end
|
264
|
+
|
241
265
|
# @private
|
242
266
|
def lexer_class; Lexer; end
|
243
267
|
|
244
268
|
def expr
|
245
269
|
interp = try_ops_after_interp([:comma], :expr) and return interp
|
246
|
-
|
270
|
+
start_pos = source_position
|
247
271
|
return unless e = interpolation
|
248
272
|
arr = [e]
|
249
273
|
while tok = try_tok(:comma)
|
@@ -253,7 +277,7 @@ RUBY
|
|
253
277
|
end
|
254
278
|
arr << assert_expr(:interpolation)
|
255
279
|
end
|
256
|
-
arr.size == 1 ? arr.first : node(List.new(arr, :comma),
|
280
|
+
arr.size == 1 ? arr.first : node(List.new(arr, :comma), start_pos)
|
257
281
|
end
|
258
282
|
|
259
283
|
production :equals, :interpolation, :single_eq
|
@@ -276,8 +300,10 @@ RUBY
|
|
276
300
|
wa = @lexer.whitespace?
|
277
301
|
str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
|
278
302
|
str.line = @lexer.line
|
303
|
+
start_pos = source_position
|
279
304
|
interp = Script::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text)
|
280
305
|
interp.line = @lexer.line
|
306
|
+
interp.source_range = range(start_pos)
|
281
307
|
return interp
|
282
308
|
end
|
283
309
|
|
@@ -295,13 +321,13 @@ RUBY
|
|
295
321
|
end
|
296
322
|
|
297
323
|
def space
|
298
|
-
|
324
|
+
start_pos = source_position
|
299
325
|
return unless e = or_expr
|
300
326
|
arr = [e]
|
301
327
|
while e = or_expr
|
302
328
|
arr << e
|
303
329
|
end
|
304
|
-
arr.size == 1 ? arr.first : node(List.new(arr, :space),
|
330
|
+
arr.size == 1 ? arr.first : node(List.new(arr, :space), start_pos)
|
305
331
|
end
|
306
332
|
|
307
333
|
production :or_expr, :and_expr, :or
|
@@ -322,16 +348,16 @@ RUBY
|
|
322
348
|
|
323
349
|
name = @lexer.next
|
324
350
|
if color = Color::COLOR_NAMES[name.value.downcase]
|
325
|
-
return node(Color.new(color))
|
351
|
+
return node(Color.new(color), token_start_position(name), source_position)
|
326
352
|
end
|
327
|
-
node(Script::String.new(name.value, :identifier))
|
353
|
+
node(Script::String.new(name.value, :identifier), token_start_position(name), source_position)
|
328
354
|
end
|
329
355
|
|
330
356
|
def funcall
|
331
357
|
return raw unless tok = try_tok(:funcall)
|
332
358
|
args, keywords, splat = fn_arglist || [[], {}]
|
333
359
|
assert_tok(:rparen)
|
334
|
-
node(Script::Funcall.new(tok.value, args, keywords, splat))
|
360
|
+
node(Script::Funcall.new(tok.value, args, keywords, splat), token_start_position(tok), source_position)
|
335
361
|
end
|
336
362
|
|
337
363
|
def defn_arglist!(must_have_parens)
|
@@ -347,9 +373,10 @@ RUBY
|
|
347
373
|
must_have_default = false
|
348
374
|
loop do
|
349
375
|
line = @lexer.line
|
350
|
-
offset = @lexer.offset
|
376
|
+
offset = @lexer.offset
|
351
377
|
c = assert_tok(:const)
|
352
378
|
var = Script::Variable.new(c.value)
|
379
|
+
var.source_range = range(c.offset)
|
353
380
|
if try_tok(:colon)
|
354
381
|
val = assert_expr(:space)
|
355
382
|
must_have_default = true
|
@@ -421,27 +448,30 @@ RUBY
|
|
421
448
|
return variable unless try_tok(:lparen)
|
422
449
|
was_in_parens = @in_parens
|
423
450
|
@in_parens = true
|
424
|
-
|
451
|
+
start_pos = source_position
|
425
452
|
e = expr
|
426
453
|
assert_tok(:rparen)
|
427
|
-
return e || node(List.new([], :space),
|
454
|
+
return e || node(List.new([], :space), start_pos)
|
428
455
|
ensure
|
429
456
|
@in_parens = was_in_parens
|
430
457
|
end
|
431
458
|
|
432
459
|
def variable
|
460
|
+
start_pos = source_position
|
433
461
|
return string unless c = try_tok(:const)
|
434
|
-
node(Variable.new(*c.value))
|
462
|
+
node(Variable.new(*c.value), start_pos, source_position)
|
435
463
|
end
|
436
464
|
|
437
465
|
def string
|
438
466
|
return number unless first = try_tok(:string)
|
439
467
|
return first.value unless try_tok(:begin_interpolation)
|
468
|
+
start_pos = source_position
|
440
469
|
line = @lexer.line
|
441
470
|
mid = parse_interpolated
|
442
471
|
last = assert_expr(:string)
|
443
472
|
interp = StringInterpolation.new(first.value, mid, last)
|
444
473
|
interp.line = line
|
474
|
+
interp.source_range = range(start_pos)
|
445
475
|
interp
|
446
476
|
end
|
447
477
|
|
@@ -486,8 +516,10 @@ RUBY
|
|
486
516
|
@lexer.expected!(EXPR_NAMES[:default])
|
487
517
|
end
|
488
518
|
|
489
|
-
def node(node,
|
490
|
-
node.line = line
|
519
|
+
def node(node, start_pos = source_position, end_pos = nil)
|
520
|
+
node.line = start_pos.line
|
521
|
+
node.filename = @options[:filename]
|
522
|
+
node.source_range = range(start_pos, end_pos) if end_pos
|
491
523
|
node
|
492
524
|
end
|
493
525
|
end
|
data/lib/sass/scss/parser.rb
CHANGED
@@ -5,16 +5,24 @@ module Sass
|
|
5
5
|
# The parser for SCSS.
|
6
6
|
# It parses a string of code into a tree of {Sass::Tree::Node}s.
|
7
7
|
class Parser
|
8
|
+
|
9
|
+
# Expose for the SASS parser.
|
10
|
+
attr_accessor :offset
|
11
|
+
|
8
12
|
# @param str [String, StringScanner] The source document to parse.
|
9
13
|
# Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
|
10
14
|
# for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
|
11
15
|
# @param filename [String] The name of the file being parsed. Used for warnings.
|
12
|
-
# @param line [Fixnum] The line on which the source string appeared,
|
16
|
+
# @param line [Fixnum] The 1-based line on which the source string appeared,
|
13
17
|
# if it's part of another document.
|
14
|
-
|
18
|
+
# @param offset [Fixnum] The 1-based character (not byte) offset in the line on
|
19
|
+
# which the source string starts. Used for error reporting and sourcemap
|
20
|
+
# building.
|
21
|
+
def initialize(str, filename, line = 1, offset = 1)
|
15
22
|
@template = str
|
16
23
|
@filename = filename
|
17
24
|
@line = line
|
25
|
+
@offset = offset
|
18
26
|
@strs = []
|
19
27
|
end
|
20
28
|
|
@@ -56,6 +64,14 @@ module Sass
|
|
56
64
|
|
57
65
|
include Sass::SCSS::RX
|
58
66
|
|
67
|
+
def source_position
|
68
|
+
Sass::Source::Position.new(@line, @offset)
|
69
|
+
end
|
70
|
+
|
71
|
+
def range(start_pos, end_pos=source_position)
|
72
|
+
Sass::Source::Range.new(start_pos, end_pos, @filename)
|
73
|
+
end
|
74
|
+
|
59
75
|
def init_scanner!
|
60
76
|
@scanner =
|
61
77
|
if @template.is_a?(StringScanner)
|
@@ -66,7 +82,7 @@ module Sass
|
|
66
82
|
end
|
67
83
|
|
68
84
|
def stylesheet
|
69
|
-
node = node(Sass::Tree::RootNode.new(@scanner.string))
|
85
|
+
node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
|
70
86
|
block_contents(node, :stylesheet) {s(node)}
|
71
87
|
end
|
72
88
|
|
@@ -128,13 +144,14 @@ module Sass
|
|
128
144
|
PREFIXED_DIRECTIVES = Set[:supports]
|
129
145
|
|
130
146
|
def directive
|
147
|
+
start_pos = source_position
|
131
148
|
return unless tok(/@/)
|
132
149
|
name = tok!(IDENT)
|
133
150
|
ss
|
134
151
|
|
135
|
-
if dir = special_directive(name)
|
152
|
+
if dir = special_directive(name, start_pos)
|
136
153
|
return dir
|
137
|
-
elsif dir = prefixed_directive(name)
|
154
|
+
elsif dir = prefixed_directive(name, start_pos)
|
138
155
|
return dir
|
139
156
|
end
|
140
157
|
|
@@ -143,11 +160,11 @@ module Sass
|
|
143
160
|
# Some take no arguments at all.
|
144
161
|
val = expr || selector
|
145
162
|
val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
|
146
|
-
directive_body(val)
|
163
|
+
directive_body(val, start_pos)
|
147
164
|
end
|
148
165
|
|
149
|
-
def directive_body(value)
|
150
|
-
node =
|
166
|
+
def directive_body(value, start_pos)
|
167
|
+
node = Sass::Tree::DirectiveNode.new(value)
|
151
168
|
|
152
169
|
if tok(/\{/)
|
153
170
|
node.has_children = true
|
@@ -155,31 +172,31 @@ module Sass
|
|
155
172
|
tok!(/\}/)
|
156
173
|
end
|
157
174
|
|
158
|
-
node
|
175
|
+
node(node, start_pos)
|
159
176
|
end
|
160
177
|
|
161
|
-
def special_directive(name)
|
178
|
+
def special_directive(name, start_pos)
|
162
179
|
sym = name.gsub('-', '_').to_sym
|
163
|
-
DIRECTIVES.include?(sym) && send("#{sym}_directive")
|
180
|
+
DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
|
164
181
|
end
|
165
182
|
|
166
|
-
def prefixed_directive(name)
|
183
|
+
def prefixed_directive(name, start_pos)
|
167
184
|
sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
|
168
|
-
PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name)
|
185
|
+
PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
|
169
186
|
end
|
170
187
|
|
171
|
-
def mixin_directive
|
188
|
+
def mixin_directive(start_pos)
|
172
189
|
name = tok! IDENT
|
173
190
|
args, splat = sass_script(:parse_mixin_definition_arglist)
|
174
191
|
ss
|
175
|
-
block(node(Sass::Tree::MixinDefNode.new(name, args, splat)), :directive)
|
192
|
+
block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
|
176
193
|
end
|
177
194
|
|
178
|
-
def include_directive
|
195
|
+
def include_directive(start_pos)
|
179
196
|
name = tok! IDENT
|
180
197
|
args, keywords, splat = sass_script(:parse_mixin_include_arglist)
|
181
198
|
ss
|
182
|
-
include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat))
|
199
|
+
include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat), start_pos)
|
183
200
|
if tok?(/\{/)
|
184
201
|
include_node.has_children = true
|
185
202
|
block(include_node, :directive)
|
@@ -188,31 +205,31 @@ module Sass
|
|
188
205
|
end
|
189
206
|
end
|
190
207
|
|
191
|
-
def content_directive
|
208
|
+
def content_directive(start_pos)
|
192
209
|
ss
|
193
|
-
node(Sass::Tree::ContentNode.new)
|
210
|
+
node(Sass::Tree::ContentNode.new, start_pos)
|
194
211
|
end
|
195
212
|
|
196
|
-
def function_directive
|
213
|
+
def function_directive(start_pos)
|
197
214
|
name = tok! IDENT
|
198
215
|
args, splat = sass_script(:parse_function_definition_arglist)
|
199
216
|
ss
|
200
|
-
block(node(Sass::Tree::FunctionNode.new(name, args, splat)), :function)
|
217
|
+
block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
|
201
218
|
end
|
202
219
|
|
203
|
-
def return_directive
|
204
|
-
node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
|
220
|
+
def return_directive(start_pos)
|
221
|
+
node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
|
205
222
|
end
|
206
223
|
|
207
|
-
def debug_directive
|
208
|
-
node(Sass::Tree::DebugNode.new(sass_script(:parse)))
|
224
|
+
def debug_directive(start_pos)
|
225
|
+
node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
|
209
226
|
end
|
210
227
|
|
211
|
-
def warn_directive
|
212
|
-
node(Sass::Tree::WarnNode.new(sass_script(:parse)))
|
228
|
+
def warn_directive(start_pos)
|
229
|
+
node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
|
213
230
|
end
|
214
231
|
|
215
|
-
def for_directive
|
232
|
+
def for_directive(start_pos)
|
216
233
|
tok!(/\$/)
|
217
234
|
var = tok! IDENT
|
218
235
|
ss
|
@@ -226,10 +243,10 @@ module Sass
|
|
226
243
|
to = sass_script(:parse)
|
227
244
|
ss
|
228
245
|
|
229
|
-
block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
|
246
|
+
block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
|
230
247
|
end
|
231
248
|
|
232
|
-
def each_directive
|
249
|
+
def each_directive(start_pos)
|
233
250
|
tok!(/\$/)
|
234
251
|
var = tok! IDENT
|
235
252
|
ss
|
@@ -238,19 +255,19 @@ module Sass
|
|
238
255
|
list = sass_script(:parse)
|
239
256
|
ss
|
240
257
|
|
241
|
-
block(node(Sass::Tree::EachNode.new(var, list)), :directive)
|
258
|
+
block(node(Sass::Tree::EachNode.new(var, list), start_pos), :directive)
|
242
259
|
end
|
243
260
|
|
244
|
-
def while_directive
|
261
|
+
def while_directive(start_pos)
|
245
262
|
expr = sass_script(:parse)
|
246
263
|
ss
|
247
|
-
block(node(Sass::Tree::WhileNode.new(expr)), :directive)
|
264
|
+
block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
|
248
265
|
end
|
249
266
|
|
250
|
-
def if_directive
|
267
|
+
def if_directive(start_pos)
|
251
268
|
expr = sass_script(:parse)
|
252
269
|
ss
|
253
|
-
node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
|
270
|
+
node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
|
254
271
|
pos = @scanner.pos
|
255
272
|
line = @line
|
256
273
|
ss
|
@@ -265,10 +282,11 @@ module Sass
|
|
265
282
|
end
|
266
283
|
|
267
284
|
def else_block(node)
|
285
|
+
start_pos = source_position
|
268
286
|
return unless tok(/@else/)
|
269
287
|
ss
|
270
288
|
else_node = block(
|
271
|
-
Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
|
289
|
+
node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
|
272
290
|
:directive)
|
273
291
|
node.add_else(else_node)
|
274
292
|
pos = @scanner.pos
|
@@ -284,18 +302,18 @@ module Sass
|
|
284
302
|
end
|
285
303
|
end
|
286
304
|
|
287
|
-
def else_directive
|
305
|
+
def else_directive(start_pos)
|
288
306
|
err("Invalid CSS: @else must come after @if")
|
289
307
|
end
|
290
308
|
|
291
|
-
def extend_directive
|
309
|
+
def extend_directive(start_pos)
|
292
310
|
selector = expr!(:selector_sequence)
|
293
311
|
optional = tok(OPTIONAL)
|
294
312
|
ss
|
295
|
-
node(Sass::Tree::ExtendNode.new(selector, !!optional))
|
313
|
+
node(Sass::Tree::ExtendNode.new(selector, !!optional), start_pos)
|
296
314
|
end
|
297
315
|
|
298
|
-
def import_directive
|
316
|
+
def import_directive(start_pos)
|
299
317
|
values = []
|
300
318
|
|
301
319
|
loop do
|
@@ -307,13 +325,13 @@ module Sass
|
|
307
325
|
end
|
308
326
|
|
309
327
|
def import_arg
|
310
|
-
|
328
|
+
start_pos = source_position
|
311
329
|
return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
|
312
330
|
if uri
|
313
331
|
str = sass_script(:parse_string)
|
314
332
|
media = media_query_list
|
315
333
|
ss
|
316
|
-
return node(Tree::CssImportNode.new(str, media.to_a))
|
334
|
+
return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
|
317
335
|
end
|
318
336
|
|
319
337
|
path = @scanner[1] || @scanner[2]
|
@@ -321,18 +339,16 @@ module Sass
|
|
321
339
|
|
322
340
|
media = media_query_list
|
323
341
|
if path =~ /^(https?:)?\/\// || media || use_css_import?
|
324
|
-
node
|
325
|
-
else
|
326
|
-
node = Sass::Tree::ImportNode.new(path.strip)
|
342
|
+
return node(Sass::Tree::CssImportNode.new(str, media.to_a), start_pos)
|
327
343
|
end
|
328
|
-
|
329
|
-
node
|
344
|
+
|
345
|
+
node(Sass::Tree::ImportNode.new(path.strip), start_pos)
|
330
346
|
end
|
331
347
|
|
332
348
|
def use_css_import?; false; end
|
333
349
|
|
334
|
-
def media_directive
|
335
|
-
block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a)), :directive)
|
350
|
+
def media_directive(start_pos)
|
351
|
+
block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
|
336
352
|
end
|
337
353
|
|
338
354
|
# http://www.w3.org/TR/css3-mediaqueries/#syntax
|
@@ -400,11 +416,11 @@ module Sass
|
|
400
416
|
res
|
401
417
|
end
|
402
418
|
|
403
|
-
def charset_directive
|
419
|
+
def charset_directive(start_pos)
|
404
420
|
tok! STRING
|
405
421
|
name = @scanner[1] || @scanner[2]
|
406
422
|
ss
|
407
|
-
node(Sass::Tree::CharsetNode.new(name))
|
423
|
+
node(Sass::Tree::CharsetNode.new(name), start_pos)
|
408
424
|
end
|
409
425
|
|
410
426
|
# The document directive is specified in
|
@@ -415,14 +431,14 @@ module Sass
|
|
415
431
|
# We could parse all document directives according to Mozilla's syntax,
|
416
432
|
# but if someone's using e.g. @-webkit-document we don't want them to
|
417
433
|
# think WebKit works sans quotes.
|
418
|
-
def _moz_document_directive
|
434
|
+
def _moz_document_directive(start_pos)
|
419
435
|
res = ["@-moz-document "]
|
420
436
|
loop do
|
421
437
|
res << str{ss} << expr!(:moz_document_function)
|
422
438
|
break unless c = tok(/,/)
|
423
439
|
res << c
|
424
440
|
end
|
425
|
-
directive_body(res.flatten)
|
441
|
+
directive_body(res.flatten, start_pos)
|
426
442
|
end
|
427
443
|
|
428
444
|
def moz_document_function
|
@@ -433,16 +449,16 @@ module Sass
|
|
433
449
|
end
|
434
450
|
|
435
451
|
# http://www.w3.org/TR/css3-conditional/
|
436
|
-
def supports_directive(name)
|
452
|
+
def supports_directive(name, start_pos)
|
437
453
|
condition = expr!(:supports_condition)
|
438
|
-
node =
|
454
|
+
node = Sass::Tree::SupportsNode.new(name, condition)
|
439
455
|
|
440
456
|
tok!(/\{/)
|
441
457
|
node.has_children = true
|
442
458
|
block_contents(node, :directive)
|
443
459
|
tok!(/\}/)
|
444
460
|
|
445
|
-
node
|
461
|
+
node(node, start_pos)
|
446
462
|
end
|
447
463
|
|
448
464
|
def supports_condition
|
@@ -494,12 +510,14 @@ module Sass
|
|
494
510
|
|
495
511
|
def variable
|
496
512
|
return unless tok(/\$/)
|
513
|
+
start_pos = source_position
|
497
514
|
name = tok!(IDENT)
|
498
515
|
ss; tok!(/:/); ss
|
499
516
|
|
500
517
|
expr = sass_script(:parse)
|
501
518
|
guarded = tok(DEFAULT)
|
502
|
-
|
519
|
+
result = Sass::Tree::VariableNode.new(name, expr, guarded)
|
520
|
+
node(result, start_pos)
|
503
521
|
end
|
504
522
|
|
505
523
|
def operator
|
@@ -511,8 +529,9 @@ module Sass
|
|
511
529
|
end
|
512
530
|
|
513
531
|
def ruleset
|
532
|
+
start_pos = source_position
|
514
533
|
return unless rules = selector_sequence
|
515
|
-
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
|
534
|
+
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact), start_pos), :ruleset)
|
516
535
|
end
|
517
536
|
|
518
537
|
def block(node, context)
|
@@ -520,6 +539,7 @@ module Sass
|
|
520
539
|
tok!(/\{/)
|
521
540
|
block_contents(node, context)
|
522
541
|
tok!(/\}/)
|
542
|
+
node.source_range.end_pos = source_position if node.source_range
|
523
543
|
node
|
524
544
|
end
|
525
545
|
|
@@ -829,6 +849,7 @@ module Sass
|
|
829
849
|
|
830
850
|
def declaration
|
831
851
|
# This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
|
852
|
+
name_start_pos = source_position
|
832
853
|
if s = tok(/[:\*\.]|\#(?!\{)/)
|
833
854
|
@use_property_exception = s !~ /[\.\#]/
|
834
855
|
name = [s, str{ss}, *expr!(:interp_ident)]
|
@@ -839,14 +860,18 @@ module Sass
|
|
839
860
|
if comment = tok(COMMENT)
|
840
861
|
name << comment
|
841
862
|
end
|
863
|
+
name_end_pos = source_position
|
842
864
|
ss
|
843
865
|
|
844
866
|
tok!(/:/)
|
845
|
-
space, value = value!
|
867
|
+
value_start_pos, space, value = value!
|
868
|
+
value_end_pos = source_position
|
846
869
|
ss
|
847
870
|
require_block = tok?(/\{/)
|
848
871
|
|
849
|
-
node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
|
872
|
+
node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new), name_start_pos, value_end_pos)
|
873
|
+
node.name_source_range = range(name_start_pos, name_end_pos)
|
874
|
+
node.value_source_range = range(value_start_pos, value_end_pos)
|
850
875
|
|
851
876
|
return node unless require_block
|
852
877
|
nested_properties! node, space
|
@@ -854,18 +879,19 @@ module Sass
|
|
854
879
|
|
855
880
|
def value!
|
856
881
|
space = !str {ss}.empty?
|
882
|
+
value_start_pos = source_position
|
857
883
|
@use_property_exception ||= space || !tok?(IDENT)
|
858
884
|
|
859
|
-
return true, Sass::Script::String.new("") if tok?(/\{/)
|
885
|
+
return value_start_pos, true, Sass::Script::String.new("") if tok?(/\{/)
|
860
886
|
# This is a bit of a dirty trick:
|
861
887
|
# if the value is completely static,
|
862
888
|
# we don't parse it at all, and instead return a plain old string
|
863
889
|
# containing the value.
|
864
890
|
# This results in a dramatic speed increase.
|
865
891
|
if val = tok(STATIC_VALUE, true)
|
866
|
-
return space, Sass::Script::String.new(val.strip)
|
892
|
+
return value_start_pos, space, Sass::Script::String.new(val.strip)
|
867
893
|
end
|
868
|
-
return space, sass_script(:parse)
|
894
|
+
return value_start_pos, space, sass_script(:parse)
|
869
895
|
end
|
870
896
|
|
871
897
|
def nested_properties!(node, space)
|
@@ -982,18 +1008,21 @@ MESSAGE
|
|
982
1008
|
def str?
|
983
1009
|
pos = @scanner.pos
|
984
1010
|
line = @line
|
1011
|
+
offset = @offset
|
985
1012
|
@strs.push ""
|
986
1013
|
throw_error {yield} && @strs.last
|
987
1014
|
rescue Sass::SyntaxError => e
|
988
1015
|
@scanner.pos = pos
|
989
1016
|
@line = line
|
1017
|
+
@offset = offset
|
990
1018
|
nil
|
991
1019
|
ensure
|
992
1020
|
@strs.pop
|
993
1021
|
end
|
994
1022
|
|
995
|
-
def node(node)
|
996
|
-
node.line =
|
1023
|
+
def node(node, start_pos, end_pos = source_position)
|
1024
|
+
node.line = start_pos.line
|
1025
|
+
node.source_range = range(start_pos, end_pos)
|
997
1026
|
node
|
998
1027
|
end
|
999
1028
|
|
@@ -1003,8 +1032,7 @@ MESSAGE
|
|
1003
1032
|
def self.sass_script_parser; @sass_script_parser; end
|
1004
1033
|
|
1005
1034
|
def sass_script(*args)
|
1006
|
-
parser = self.class.sass_script_parser.new(@scanner, @line,
|
1007
|
-
@scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
|
1035
|
+
parser = self.class.sass_script_parser.new(@scanner, @line, @offset, :filename => @filename)
|
1008
1036
|
result = parser.send(*args)
|
1009
1037
|
unless @strs.empty?
|
1010
1038
|
# Convert to CSS manually so that comments are ignored.
|
@@ -1012,6 +1040,7 @@ MESSAGE
|
|
1012
1040
|
@strs.each {|s| s << src}
|
1013
1041
|
end
|
1014
1042
|
@line = parser.line
|
1043
|
+
@offset = parser.offset
|
1015
1044
|
result
|
1016
1045
|
rescue Sass::SyntaxError => e
|
1017
1046
|
throw(:_sass_parser_error, true) if @throw_error
|
@@ -1086,10 +1115,12 @@ MESSAGE
|
|
1086
1115
|
old_throw_error, @throw_error = @throw_error, true
|
1087
1116
|
pos = @scanner.pos
|
1088
1117
|
line = @line
|
1118
|
+
offset = @offset
|
1089
1119
|
expected = @expected
|
1090
1120
|
if catch(:_sass_parser_error) {yield; false}
|
1091
1121
|
@scanner.pos = pos
|
1092
1122
|
@line = line
|
1123
|
+
@offset = offset
|
1093
1124
|
@expected = expected
|
1094
1125
|
{:pos => pos, :line => line, :expected => @expected, :block => block}
|
1095
1126
|
end
|
@@ -1152,7 +1183,15 @@ MESSAGE
|
|
1152
1183
|
@scanner.pos -= @scanner[-1].length
|
1153
1184
|
res.slice!(-@scanner[-1].length..-1)
|
1154
1185
|
end
|
1155
|
-
|
1186
|
+
|
1187
|
+
newline_count = res.count(NEWLINE)
|
1188
|
+
if newline_count > 0
|
1189
|
+
@line += newline_count
|
1190
|
+
@offset = res[res.rindex(NEWLINE)..-1].size
|
1191
|
+
else
|
1192
|
+
@offset += res.size
|
1193
|
+
end
|
1194
|
+
|
1156
1195
|
@expected = nil
|
1157
1196
|
if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
|
1158
1197
|
@strs.each {|s| s << res}
|