sass 3.2.7 → 3.3.0.rc.1

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.
Files changed (184) hide show
  1. data/MIT-LICENSE +2 -2
  2. data/README.md +14 -2
  3. data/Rakefile +25 -1
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/lib/sass/cache_stores/base.rb +4 -2
  8. data/lib/sass/cache_stores/chain.rb +2 -1
  9. data/lib/sass/cache_stores/filesystem.rb +2 -6
  10. data/lib/sass/cache_stores/memory.rb +1 -1
  11. data/lib/sass/cache_stores/null.rb +2 -2
  12. data/lib/sass/callbacks.rb +1 -0
  13. data/lib/sass/css.rb +10 -10
  14. data/lib/sass/engine.rb +403 -150
  15. data/lib/sass/environment.rb +136 -57
  16. data/lib/sass/error.rb +7 -7
  17. data/lib/sass/exec.rb +123 -39
  18. data/lib/sass/features.rb +41 -0
  19. data/lib/sass/importers/base.rb +33 -2
  20. data/lib/sass/importers/deprecated_path.rb +45 -0
  21. data/lib/sass/importers/filesystem.rb +25 -14
  22. data/lib/sass/importers.rb +1 -0
  23. data/lib/sass/logger/base.rb +3 -3
  24. data/lib/sass/logger/log_level.rb +4 -6
  25. data/lib/sass/media.rb +19 -19
  26. data/lib/sass/plugin/compiler.rb +141 -101
  27. data/lib/sass/plugin/configuration.rb +18 -22
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/staleness_checker.rb +24 -8
  30. data/lib/sass/plugin.rb +4 -2
  31. data/lib/sass/repl.rb +3 -3
  32. data/lib/sass/script/css_lexer.rb +9 -4
  33. data/lib/sass/script/css_parser.rb +6 -2
  34. data/lib/sass/script/functions.rb +1343 -590
  35. data/lib/sass/script/lexer.rb +84 -52
  36. data/lib/sass/script/parser.rb +217 -97
  37. data/lib/sass/script/tree/funcall.rb +290 -0
  38. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +34 -13
  39. data/lib/sass/script/tree/list_literal.rb +80 -0
  40. data/lib/sass/script/tree/literal.rb +47 -0
  41. data/lib/sass/script/tree/map_literal.rb +64 -0
  42. data/lib/sass/script/{node.rb → tree/node.rb} +22 -12
  43. data/lib/sass/script/{operation.rb → tree/operation.rb} +17 -25
  44. data/lib/sass/script/tree/selector.rb +30 -0
  45. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +5 -4
  46. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +14 -9
  47. data/lib/sass/script/tree/variable.rb +57 -0
  48. data/lib/sass/script/tree.rb +16 -0
  49. data/lib/sass/script/{arg_list.rb → value/arg_list.rb} +4 -24
  50. data/lib/sass/script/value/base.rb +248 -0
  51. data/lib/sass/script/value/bool.rb +36 -0
  52. data/lib/sass/script/{color.rb → value/color.rb} +239 -195
  53. data/lib/sass/script/value/helpers.rb +155 -0
  54. data/lib/sass/script/value/list.rb +119 -0
  55. data/lib/sass/script/value/map.rb +70 -0
  56. data/lib/sass/script/value/null.rb +45 -0
  57. data/lib/sass/script/{number.rb → value/number.rb} +91 -65
  58. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  59. data/lib/sass/script/value.rb +11 -0
  60. data/lib/sass/script.rb +35 -8
  61. data/lib/sass/scss/css_parser.rb +2 -1
  62. data/lib/sass/scss/parser.rb +338 -170
  63. data/lib/sass/scss/rx.rb +5 -6
  64. data/lib/sass/scss/script_lexer.rb +1 -0
  65. data/lib/sass/scss/script_parser.rb +1 -0
  66. data/lib/sass/scss/static_parser.rb +23 -6
  67. data/lib/sass/selector/abstract_sequence.rb +2 -2
  68. data/lib/sass/selector/comma_sequence.rb +21 -16
  69. data/lib/sass/selector/sequence.rb +60 -34
  70. data/lib/sass/selector/simple.rb +11 -12
  71. data/lib/sass/selector/simple_sequence.rb +55 -33
  72. data/lib/sass/selector.rb +52 -48
  73. data/lib/sass/source/map.rb +211 -0
  74. data/lib/sass/source/position.rb +39 -0
  75. data/lib/sass/source/range.rb +41 -0
  76. data/lib/sass/stack.rb +120 -0
  77. data/lib/sass/supports.rb +12 -13
  78. data/lib/sass/tree/at_root_node.rb +82 -0
  79. data/lib/sass/tree/comment_node.rb +3 -3
  80. data/lib/sass/tree/css_import_node.rb +11 -11
  81. data/lib/sass/tree/debug_node.rb +2 -2
  82. data/lib/sass/tree/directive_node.rb +13 -2
  83. data/lib/sass/tree/each_node.rb +8 -8
  84. data/lib/sass/tree/extend_node.rb +13 -6
  85. data/lib/sass/tree/for_node.rb +4 -4
  86. data/lib/sass/tree/function_node.rb +5 -4
  87. data/lib/sass/tree/if_node.rb +1 -1
  88. data/lib/sass/tree/import_node.rb +4 -5
  89. data/lib/sass/tree/media_node.rb +4 -14
  90. data/lib/sass/tree/mixin_def_node.rb +4 -4
  91. data/lib/sass/tree/mixin_node.rb +21 -8
  92. data/lib/sass/tree/node.rb +29 -12
  93. data/lib/sass/tree/prop_node.rb +38 -18
  94. data/lib/sass/tree/return_node.rb +3 -2
  95. data/lib/sass/tree/root_node.rb +19 -3
  96. data/lib/sass/tree/rule_node.rb +25 -17
  97. data/lib/sass/tree/supports_node.rb +0 -13
  98. data/lib/sass/tree/trace_node.rb +2 -1
  99. data/lib/sass/tree/variable_node.rb +9 -3
  100. data/lib/sass/tree/visitors/base.rb +6 -6
  101. data/lib/sass/tree/visitors/check_nesting.rb +12 -9
  102. data/lib/sass/tree/visitors/convert.rb +63 -38
  103. data/lib/sass/tree/visitors/cssize.rb +63 -23
  104. data/lib/sass/tree/visitors/deep_copy.rb +6 -5
  105. data/lib/sass/tree/visitors/extend.rb +7 -7
  106. data/lib/sass/tree/visitors/perform.rb +256 -151
  107. data/lib/sass/tree/visitors/set_options.rb +6 -6
  108. data/lib/sass/tree/visitors/to_css.rb +231 -81
  109. data/lib/sass/tree/warn_node.rb +2 -2
  110. data/lib/sass/tree/while_node.rb +2 -2
  111. data/lib/sass/util/multibyte_string_scanner.rb +2 -0
  112. data/lib/sass/util/normalized_map.rb +65 -0
  113. data/lib/sass/util/ordered_hash.rb +188 -0
  114. data/lib/sass/util/subset_map.rb +3 -2
  115. data/lib/sass/util/test.rb +9 -0
  116. data/lib/sass/util.rb +220 -34
  117. data/lib/sass/version.rb +9 -9
  118. data/lib/sass.rb +14 -7
  119. data/test/sass/compiler_test.rb +213 -0
  120. data/test/sass/conversion_test.rb +235 -9
  121. data/test/sass/engine_test.rb +230 -60
  122. data/test/sass/exec_test.rb +86 -0
  123. data/test/sass/extend_test.rb +215 -147
  124. data/test/sass/functions_test.rb +584 -99
  125. data/test/sass/importer_test.rb +165 -17
  126. data/test/sass/plugin_test.rb +19 -13
  127. data/test/sass/script_conversion_test.rb +40 -0
  128. data/test/sass/script_test.rb +231 -21
  129. data/test/sass/scss/css_test.rb +14 -5
  130. data/test/sass/scss/scss_test.rb +1266 -66
  131. data/test/sass/source_map_test.rb +879 -0
  132. data/test/sass/templates/bork5.sass +3 -0
  133. data/test/sass/util/normalized_map_test.rb +30 -0
  134. data/test/sass/util_test.rb +90 -0
  135. data/test/sass/value_helpers_test.rb +181 -0
  136. data/test/test_helper.rb +7 -2
  137. metadata +316 -291
  138. data/lib/sass/script/bool.rb +0 -18
  139. data/lib/sass/script/funcall.rb +0 -231
  140. data/lib/sass/script/list.rb +0 -84
  141. data/lib/sass/script/literal.rb +0 -239
  142. data/lib/sass/script/null.rb +0 -34
  143. data/lib/sass/script/variable.rb +0 -58
  144. data/test/Gemfile +0 -3
  145. data/vendor/listen/CHANGELOG.md +0 -221
  146. data/vendor/listen/CONTRIBUTING.md +0 -38
  147. data/vendor/listen/Gemfile +0 -30
  148. data/vendor/listen/Guardfile +0 -8
  149. data/vendor/listen/LICENSE +0 -20
  150. data/vendor/listen/README.md +0 -315
  151. data/vendor/listen/Rakefile +0 -47
  152. data/vendor/listen/Vagrantfile +0 -96
  153. data/vendor/listen/lib/listen/adapter.rb +0 -214
  154. data/vendor/listen/lib/listen/adapters/bsd.rb +0 -112
  155. data/vendor/listen/lib/listen/adapters/darwin.rb +0 -85
  156. data/vendor/listen/lib/listen/adapters/linux.rb +0 -113
  157. data/vendor/listen/lib/listen/adapters/polling.rb +0 -67
  158. data/vendor/listen/lib/listen/adapters/windows.rb +0 -87
  159. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  160. data/vendor/listen/lib/listen/directory_record.rb +0 -371
  161. data/vendor/listen/lib/listen/listener.rb +0 -225
  162. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  163. data/vendor/listen/lib/listen/turnstile.rb +0 -28
  164. data/vendor/listen/lib/listen/version.rb +0 -3
  165. data/vendor/listen/lib/listen.rb +0 -40
  166. data/vendor/listen/listen.gemspec +0 -22
  167. data/vendor/listen/spec/listen/adapter_spec.rb +0 -183
  168. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
  169. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
  170. data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
  171. data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
  172. data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
  173. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  174. data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1225
  175. data/vendor/listen/spec/listen/listener_spec.rb +0 -169
  176. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
  177. data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
  178. data/vendor/listen/spec/listen_spec.rb +0 -73
  179. data/vendor/listen/spec/spec_helper.rb +0 -21
  180. data/vendor/listen/spec/support/adapter_helper.rb +0 -629
  181. data/vendor/listen/spec/support/directory_record_helper.rb +0 -55
  182. data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
  183. data/vendor/listen/spec/support/listeners_helper.rb +0 -156
  184. data/vendor/listen/spec/support/platform_helper.rb +0 -15
@@ -5,16 +5,31 @@ 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
- # @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,
15
+ # @param filename [String] The name of the file being parsed. Used for
16
+ # warnings and source maps.
17
+ # @param importer [Sass::Importers::Base] The importer used to import the
18
+ # file being parsed. Used for source maps.
19
+ # @param line [Fixnum] The 1-based line on which the source string appeared,
13
20
  # if it's part of another document.
14
- def initialize(str, filename, line = 1)
21
+ # @param offset [Fixnum] The 1-based character (not byte) offset in the line on
22
+ # which the source string starts. Used for error reporting and sourcemap
23
+ # building.
24
+ # @comment
25
+ # rubocop:disable ParameterLists
26
+ def initialize(str, filename, importer, line = 1, offset = 1)
27
+ # rubocop:enable ParameterLists
15
28
  @template = str
16
29
  @filename = filename
30
+ @importer = importer
17
31
  @line = line
32
+ @offset = offset
18
33
  @strs = []
19
34
  end
20
35
 
@@ -33,7 +48,7 @@ module Sass
33
48
  # Note that this won't assert that the identifier takes up the entire input string;
34
49
  # it's meant to be used with `StringScanner`s as part of other parsers.
35
50
  #
36
- # @return [Array<String, Sass::Script::Node>, nil]
51
+ # @return [Array<String, Sass::Script::Tree::Node>, nil]
37
52
  # The interpolated identifier, or nil if none could be parsed
38
53
  def parse_interp_ident
39
54
  init_scanner!
@@ -52,6 +67,18 @@ module Sass
52
67
  ql
53
68
  end
54
69
 
70
+ # Parses an at-root query.
71
+ #
72
+ # @return [Array<String, Sass::Script;:Tree::Node>] The interpolated query.
73
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query,
74
+ # or if it doesn't take up the entire input string.
75
+ def parse_at_root_query
76
+ init_scanner!
77
+ query = at_root_query
78
+ expected("@at-root query list") unless @scanner.eos?
79
+ query
80
+ end
81
+
55
82
  # Parses a supports query condition.
56
83
  #
57
84
  # @return [Sass::Supports::Condition] The parsed condition
@@ -68,6 +95,14 @@ module Sass
68
95
 
69
96
  include Sass::SCSS::RX
70
97
 
98
+ def source_position
99
+ Sass::Source::Position.new(@line, @offset)
100
+ end
101
+
102
+ def range(start_pos, end_pos = source_position)
103
+ Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
104
+ end
105
+
71
106
  def init_scanner!
72
107
  @scanner =
73
108
  if @template.is_a?(StringScanner)
@@ -78,7 +113,7 @@ module Sass
78
113
  end
79
114
 
80
115
  def stylesheet
81
- node = node(Sass::Tree::RootNode.new(@scanner.string))
116
+ node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
82
117
  block_contents(node, :stylesheet) {s(node)}
83
118
  end
84
119
 
@@ -112,22 +147,28 @@ module Sass
112
147
  end
113
148
 
114
149
  def process_comment(text, node)
115
- silent = text =~ /^\/\//
150
+ silent = text =~ %r{^//}
116
151
  loud = !silent && text =~ %r{^/[/*]!}
117
152
  line = @line - text.count("\n")
118
153
 
119
154
  if silent
120
- value = [text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */']
155
+ value = [text.sub(%r{^\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
121
156
  else
122
- value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
123
- value[0].slice!(2) if loud # get rid of the "!"
157
+ value = Sass::Engine.parse_interp(
158
+ text, line, @scanner.pos - text.size, :filename => @filename)
124
159
  value.unshift(@scanner.
125
160
  string[0...@scanner.pos].
126
161
  reverse[/.*?\*\/(.*?)($|\Z)/, 1].
127
162
  reverse.gsub(/[^\s]/, ' '))
128
163
  end
129
164
 
130
- type = if silent then :silent elsif loud then :loud else :normal end
165
+ type = if silent
166
+ :silent
167
+ elsif loud
168
+ :loud
169
+ else
170
+ :normal
171
+ end
131
172
  comment = Sass::Tree::CommentNode.new(value, type)
132
173
  comment.line = line
133
174
  node << comment
@@ -135,18 +176,19 @@ module Sass
135
176
 
136
177
  DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
137
178
  :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
138
- :_moz_document]
179
+ :_moz_document, :at_root]
139
180
 
140
181
  PREFIXED_DIRECTIVES = Set[:supports]
141
182
 
142
183
  def directive
184
+ start_pos = source_position
143
185
  return unless tok(/@/)
144
186
  name = tok!(IDENT)
145
187
  ss
146
188
 
147
- if dir = special_directive(name)
189
+ if (dir = special_directive(name, start_pos))
148
190
  return dir
149
- elsif dir = prefixed_directive(name)
191
+ elsif (dir = prefixed_directive(name, start_pos))
150
192
  return dir
151
193
  end
152
194
 
@@ -155,11 +197,11 @@ module Sass
155
197
  # Some take no arguments at all.
156
198
  val = expr || selector
157
199
  val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
158
- directive_body(val)
200
+ directive_body(val, start_pos)
159
201
  end
160
202
 
161
- def directive_body(value)
162
- node = node(Sass::Tree::DirectiveNode.new(value))
203
+ def directive_body(value, start_pos)
204
+ node = Sass::Tree::DirectiveNode.new(value)
163
205
 
164
206
  if tok(/\{/)
165
207
  node.has_children = true
@@ -167,31 +209,32 @@ module Sass
167
209
  tok!(/\}/)
168
210
  end
169
211
 
170
- node
212
+ node(node, start_pos)
171
213
  end
172
214
 
173
- def special_directive(name)
215
+ def special_directive(name, start_pos)
174
216
  sym = name.gsub('-', '_').to_sym
175
- DIRECTIVES.include?(sym) && send("#{sym}_directive")
217
+ DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
176
218
  end
177
219
 
178
- def prefixed_directive(name)
220
+ def prefixed_directive(name, start_pos)
179
221
  sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
180
- PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name)
222
+ PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
181
223
  end
182
224
 
183
- def mixin_directive
225
+ def mixin_directive(start_pos)
184
226
  name = tok! IDENT
185
227
  args, splat = sass_script(:parse_mixin_definition_arglist)
186
228
  ss
187
- block(node(Sass::Tree::MixinDefNode.new(name, args, splat)), :directive)
229
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
188
230
  end
189
231
 
190
- def include_directive
232
+ def include_directive(start_pos)
191
233
  name = tok! IDENT
192
- args, keywords, splat = sass_script(:parse_mixin_include_arglist)
234
+ args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
193
235
  ss
194
- include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat))
236
+ include_node = node(
237
+ Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
195
238
  if tok?(/\{/)
196
239
  include_node.has_children = true
197
240
  block(include_node, :directive)
@@ -200,31 +243,31 @@ module Sass
200
243
  end
201
244
  end
202
245
 
203
- def content_directive
246
+ def content_directive(start_pos)
204
247
  ss
205
- node(Sass::Tree::ContentNode.new)
248
+ node(Sass::Tree::ContentNode.new, start_pos)
206
249
  end
207
250
 
208
- def function_directive
251
+ def function_directive(start_pos)
209
252
  name = tok! IDENT
210
253
  args, splat = sass_script(:parse_function_definition_arglist)
211
254
  ss
212
- block(node(Sass::Tree::FunctionNode.new(name, args, splat)), :function)
255
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
213
256
  end
214
257
 
215
- def return_directive
216
- node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
258
+ def return_directive(start_pos)
259
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
217
260
  end
218
261
 
219
- def debug_directive
220
- node(Sass::Tree::DebugNode.new(sass_script(:parse)))
262
+ def debug_directive(start_pos)
263
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
221
264
  end
222
265
 
223
- def warn_directive
224
- node(Sass::Tree::WarnNode.new(sass_script(:parse)))
266
+ def warn_directive(start_pos)
267
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
225
268
  end
226
269
 
227
- def for_directive
270
+ def for_directive(start_pos)
228
271
  tok!(/\$/)
229
272
  var = tok! IDENT
230
273
  ss
@@ -238,31 +281,37 @@ module Sass
238
281
  to = sass_script(:parse)
239
282
  ss
240
283
 
241
- block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
284
+ block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
242
285
  end
243
286
 
244
- def each_directive
287
+ def each_directive(start_pos)
245
288
  tok!(/\$/)
246
- var = tok! IDENT
289
+ vars = [tok!(IDENT)]
247
290
  ss
291
+ while tok(/,/)
292
+ ss
293
+ tok!(/\$/)
294
+ vars << tok!(IDENT)
295
+ ss
296
+ end
248
297
 
249
298
  tok!(/in/)
250
299
  list = sass_script(:parse)
251
300
  ss
252
301
 
253
- block(node(Sass::Tree::EachNode.new(var, list)), :directive)
302
+ block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
254
303
  end
255
304
 
256
- def while_directive
305
+ def while_directive(start_pos)
257
306
  expr = sass_script(:parse)
258
307
  ss
259
- block(node(Sass::Tree::WhileNode.new(expr)), :directive)
308
+ block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
260
309
  end
261
310
 
262
- def if_directive
311
+ def if_directive(start_pos)
263
312
  expr = sass_script(:parse)
264
313
  ss
265
- node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
314
+ node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
266
315
  pos = @scanner.pos
267
316
  line = @line
268
317
  ss
@@ -277,10 +326,11 @@ module Sass
277
326
  end
278
327
 
279
328
  def else_block(node)
329
+ start_pos = source_position
280
330
  return unless tok(/@else/)
281
331
  ss
282
332
  else_node = block(
283
- Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
333
+ node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
284
334
  :directive)
285
335
  node.add_else(else_node)
286
336
  pos = @scanner.pos
@@ -296,18 +346,18 @@ module Sass
296
346
  end
297
347
  end
298
348
 
299
- def else_directive
349
+ def else_directive(start_pos)
300
350
  err("Invalid CSS: @else must come after @if")
301
351
  end
302
352
 
303
- def extend_directive
304
- selector = expr!(:selector_sequence)
353
+ def extend_directive(start_pos)
354
+ selector, selector_range = expr!(:selector_sequence)
305
355
  optional = tok(OPTIONAL)
306
356
  ss
307
- node(Sass::Tree::ExtendNode.new(selector, !!optional))
357
+ node(Sass::Tree::ExtendNode.new(selector, !!optional, selector_range), start_pos)
308
358
  end
309
359
 
310
- def import_directive
360
+ def import_directive(start_pos)
311
361
  values = []
312
362
 
313
363
  loop do
@@ -317,41 +367,40 @@ module Sass
317
367
  ss
318
368
  end
319
369
 
320
- return values
370
+ values
321
371
  end
322
372
 
323
373
  def import_arg
324
- line = @line
374
+ start_pos = source_position
325
375
  return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
326
376
  if uri
327
377
  str = sass_script(:parse_string)
328
378
  media = media_query_list
329
379
  ss
330
- return node(Tree::CssImportNode.new(str, media.to_a))
380
+ return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
331
381
  end
332
382
 
333
383
  path = @scanner[1] || @scanner[2]
334
384
  ss
335
385
 
336
386
  media = media_query_list
337
- if path =~ /^(https?:)?\/\// || media || use_css_import?
338
- node = Sass::Tree::CssImportNode.new(str, media.to_a)
339
- else
340
- node = Sass::Tree::ImportNode.new(path.strip)
387
+ if path =~ %r{^(https?:)?//} || media || use_css_import?
388
+ return node(Sass::Tree::CssImportNode.new(str, media.to_a), start_pos)
341
389
  end
342
- node.line = line
343
- node
390
+
391
+ node(Sass::Tree::ImportNode.new(path.strip), start_pos)
344
392
  end
345
393
 
346
394
  def use_css_import?; false; end
347
395
 
348
- def media_directive
349
- block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a)), :directive)
396
+ def media_directive(start_pos)
397
+ block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
350
398
  end
351
399
 
352
400
  # http://www.w3.org/TR/css3-mediaqueries/#syntax
353
401
  def media_query_list
354
- return unless query = media_query
402
+ query = media_query
403
+ return unless query
355
404
  queries = [query]
356
405
 
357
406
  ss
@@ -364,7 +413,7 @@ module Sass
364
413
  end
365
414
 
366
415
  def media_query
367
- if ident1 = interp_ident
416
+ if (ident1 = interp_ident)
368
417
  ss
369
418
  ident2 = interp_ident
370
419
  ss
@@ -384,7 +433,8 @@ module Sass
384
433
  if query
385
434
  expr = expr!(:media_expr)
386
435
  else
387
- return unless expr = media_expr
436
+ expr = media_expr
437
+ return unless expr
388
438
  end
389
439
  query ||= Sass::Media::Query.new([], [], [])
390
440
  query.expressions << expr
@@ -397,8 +447,9 @@ module Sass
397
447
  query
398
448
  end
399
449
 
400
- def media_expr
401
- interp = interpolation and return interp
450
+ def query_expr
451
+ interp = interpolation
452
+ return interp if interp
402
453
  return unless tok(/\(/)
403
454
  res = ['(']
404
455
  ss
@@ -414,11 +465,16 @@ module Sass
414
465
  res
415
466
  end
416
467
 
417
- def charset_directive
468
+ # Aliases allow us to use different descriptions if the same
469
+ # expression fails in different contexts.
470
+ alias_method :media_expr, :query_expr
471
+ alias_method :at_root_query, :query_expr
472
+
473
+ def charset_directive(start_pos)
418
474
  tok! STRING
419
475
  name = @scanner[1] || @scanner[2]
420
476
  ss
421
- node(Sass::Tree::CharsetNode.new(name))
477
+ node(Sass::Tree::CharsetNode.new(name), start_pos)
422
478
  end
423
479
 
424
480
  # The document directive is specified in
@@ -429,34 +485,61 @@ module Sass
429
485
  # We could parse all document directives according to Mozilla's syntax,
430
486
  # but if someone's using e.g. @-webkit-document we don't want them to
431
487
  # think WebKit works sans quotes.
432
- def _moz_document_directive
488
+ def _moz_document_directive(start_pos)
433
489
  res = ["@-moz-document "]
434
490
  loop do
435
- res << str{ss} << expr!(:moz_document_function)
436
- break unless c = tok(/,/)
437
- res << c
491
+ res << str {ss} << expr!(:moz_document_function)
492
+ if (c = tok(/,/))
493
+ res << c
494
+ else
495
+ break
496
+ end
438
497
  end
439
- directive_body(res.flatten)
498
+ directive_body(res.flatten, start_pos)
440
499
  end
441
500
 
442
501
  def moz_document_function
443
- return unless val = interp_uri || _interp_string(:url_prefix) ||
502
+ val = interp_uri || _interp_string(:url_prefix) ||
444
503
  _interp_string(:domain) || function(!:allow_var) || interpolation
504
+ return unless val
445
505
  ss
446
506
  val
447
507
  end
448
508
 
509
+ def at_root_directive(start_pos)
510
+ if tok?(/\(/) && (expr = at_root_query)
511
+ return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive)
512
+ end
513
+
514
+ at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
515
+ rule_node = ruleset
516
+ return block(at_root_node, :stylesheet) unless rule_node
517
+ at_root_node << rule_node
518
+ at_root_node
519
+ end
520
+
521
+ def at_root_directive_list
522
+ return unless (first = tok(IDENT))
523
+ arr = [first]
524
+ ss
525
+ while (e = tok(IDENT))
526
+ arr << e
527
+ ss
528
+ end
529
+ arr
530
+ end
531
+
449
532
  # http://www.w3.org/TR/css3-conditional/
450
- def supports_directive(name)
533
+ def supports_directive(name, start_pos)
451
534
  condition = expr!(:supports_condition)
452
- node = node(Sass::Tree::SupportsNode.new(name, condition))
535
+ node = Sass::Tree::SupportsNode.new(name, condition)
453
536
 
454
537
  tok!(/\{/)
455
538
  node.has_children = true
456
539
  block_contents(node, :directive)
457
540
  tok!(/\}/)
458
541
 
459
- node
542
+ node(node, start_pos)
460
543
  end
461
544
 
462
545
  def supports_condition
@@ -470,20 +553,21 @@ module Sass
470
553
  end
471
554
 
472
555
  def supports_operator
473
- return unless cond = supports_condition_in_parens
474
- return cond unless op = tok(/and|or/i)
475
- begin
556
+ cond = supports_condition_in_parens
557
+ return unless cond
558
+ while (op = tok(/and|or/i))
476
559
  ss
477
560
  cond = Sass::Supports::Operator.new(
478
561
  cond, expr!(:supports_condition_in_parens), op)
479
- end while op = tok(/and|or/i)
562
+ end
480
563
  cond
481
564
  end
482
565
 
483
566
  def supports_condition_in_parens
484
- interp = supports_interpolation and return interp
567
+ interp = supports_interpolation
568
+ return interp if interp
485
569
  return unless tok(/\(/); ss
486
- if cond = supports_condition
570
+ if (cond = supports_condition)
487
571
  tok!(/\)/); ss
488
572
  cond
489
573
  else
@@ -501,19 +585,33 @@ module Sass
501
585
  end
502
586
 
503
587
  def supports_interpolation
504
- return unless interp = interpolation
588
+ interp = interpolation
589
+ return unless interp
505
590
  ss
506
591
  Sass::Supports::Interpolation.new(interp)
507
592
  end
508
593
 
509
594
  def variable
510
595
  return unless tok(/\$/)
596
+ start_pos = source_position
511
597
  name = tok!(IDENT)
512
598
  ss; tok!(/:/); ss
513
599
 
514
600
  expr = sass_script(:parse)
515
- guarded = tok(DEFAULT)
516
- node(Sass::Tree::VariableNode.new(name, expr, guarded))
601
+ while tok(/!/)
602
+ flag_name = tok!(IDENT)
603
+ if flag_name == 'default'
604
+ guarded ||= true
605
+ elsif flag_name == 'global'
606
+ global ||= true
607
+ else
608
+ raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line)
609
+ end
610
+ ss
611
+ end
612
+
613
+ result = Sass::Tree::VariableNode.new(name, expr, guarded, global)
614
+ node(result, start_pos)
517
615
  end
518
616
 
519
617
  def operator
@@ -525,8 +623,11 @@ module Sass
525
623
  end
526
624
 
527
625
  def ruleset
528
- return unless rules = selector_sequence
529
- block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
626
+ start_pos = source_position
627
+ rules, source_range = selector_sequence
628
+ return unless rules
629
+ block(node(
630
+ Sass::Tree::RuleNode.new(rules.flatten.compact, source_range), start_pos), :ruleset)
530
631
  end
531
632
 
532
633
  def block(node, context)
@@ -557,7 +658,7 @@ module Sass
557
658
  def has_children?(child_or_array)
558
659
  return false unless child_or_array
559
660
  return child_or_array.last.has_children if child_or_array.is_a?(Array)
560
- return child_or_array.has_children
661
+ child_or_array.has_children
561
662
  end
562
663
 
563
664
  # This is a nasty hack, and the only place in the parser
@@ -595,40 +696,46 @@ module Sass
595
696
  end
596
697
 
597
698
  def selector_sequence
598
- if sel = tok(STATIC_SELECTOR, true)
599
- return [sel]
699
+ start_pos = source_position
700
+ if (sel = tok(STATIC_SELECTOR, true))
701
+ return [sel], range(start_pos)
600
702
  end
601
703
 
602
704
  rules = []
603
- return unless v = selector
705
+ v = selector
706
+ return unless v
604
707
  rules.concat v
605
708
 
606
709
  ws = ''
607
710
  while tok(/,/)
608
711
  ws << str {ss}
609
- if v = selector
712
+ if (v = selector)
610
713
  rules << ',' << ws
611
714
  rules.concat v
612
715
  ws = ''
613
716
  end
614
717
  end
615
- rules
718
+ return rules, range(start_pos)
616
719
  end
617
720
 
618
721
  def selector
619
- return unless sel = _selector
722
+ sel = _selector
723
+ return unless sel
620
724
  sel.to_a
621
725
  end
622
726
 
623
727
  def selector_comma_sequence
624
- return unless sel = _selector
728
+ sel = _selector
729
+ return unless sel
625
730
  selectors = [sel]
626
731
  ws = ''
627
732
  while tok(/,/)
628
- ws << str{ss}
629
- if sel = _selector
733
+ ws << str {ss}
734
+ if (sel = _selector)
630
735
  selectors << sel
631
- selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
736
+ if ws.include?("\n")
737
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members)
738
+ end
632
739
  ws = ''
633
740
  end
634
741
  end
@@ -637,15 +744,16 @@ module Sass
637
744
 
638
745
  def _selector
639
746
  # The combinator here allows the "> E" hack
640
- return unless val = combinator || simple_selector_sequence
641
- nl = str{ss}.include?("\n")
747
+ val = combinator || simple_selector_sequence
748
+ return unless val
749
+ nl = str {ss}.include?("\n")
642
750
  res = []
643
751
  res << val
644
752
  res << "\n" if nl
645
753
 
646
- while val = combinator || simple_selector_sequence
754
+ while (val = combinator || simple_selector_sequence)
647
755
  res << val
648
- res << "\n" if str{ss}.include?("\n")
756
+ res << "\n" if str {ss}.include?("\n")
649
757
  end
650
758
  Selector::Sequence.new(res.compact)
651
759
  end
@@ -668,21 +776,24 @@ module Sass
668
776
  def simple_selector_sequence
669
777
  # Returning expr by default allows for stuff like
670
778
  # http://www.w3.org/TR/css3-animations/#keyframes-
671
- return expr(!:allow_var) unless e = element_name || id_selector ||
779
+
780
+ start_pos = source_position
781
+ e = element_name || id_selector ||
672
782
  class_selector || placeholder_selector || attrib || pseudo ||
673
783
  parent_selector || interpolation_selector
784
+ return expr(!:allow_var) unless e
674
785
  res = [e]
675
786
 
676
787
  # The tok(/\*/) allows the "E*" hack
677
- while v = id_selector || class_selector || placeholder_selector || attrib ||
678
- pseudo || interpolation_selector ||
679
- (tok(/\*/) && Selector::Universal.new(nil))
788
+ while (v = id_selector || class_selector || placeholder_selector ||
789
+ attrib || pseudo || interpolation_selector ||
790
+ (tok(/\*/) && Selector::Universal.new(nil)))
680
791
  res << v
681
792
  end
682
793
 
683
794
  pos = @scanner.pos
684
795
  line = @line
685
- if sel = str? {simple_selector_sequence}
796
+ if (sel = str? {simple_selector_sequence})
686
797
  @scanner.pos = pos
687
798
  @line = line
688
799
  begin
@@ -696,7 +807,7 @@ module Sass
696
807
  end
697
808
  end
698
809
 
699
- Selector::SimpleSequence.new(res, tok(/!/))
810
+ Selector::SimpleSequence.new(res, tok(/!/), range(start_pos))
700
811
  end
701
812
 
702
813
  def parent_selector
@@ -733,8 +844,9 @@ module Sass
733
844
  end
734
845
  end
735
846
 
736
- def qualified_name(allow_star_name=false)
737
- return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
847
+ def qualified_name(allow_star_name = false)
848
+ name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
849
+ return unless name
738
850
  return nil, name unless tok(/\|/)
739
851
 
740
852
  return name, expr!(:interp_ident) unless allow_star_name
@@ -743,8 +855,9 @@ module Sass
743
855
  end
744
856
 
745
857
  def interpolation_selector
746
- return unless script = interpolation
747
- Selector::Interpolation.new(script)
858
+ if (script = interpolation)
859
+ Selector::Interpolation.new(script)
860
+ end
748
861
  end
749
862
 
750
863
  def attrib
@@ -753,12 +866,13 @@ module Sass
753
866
  ns, name = attrib_name!
754
867
  ss
755
868
 
756
- if op = tok(/=/) ||
757
- tok(INCLUDES) ||
758
- tok(DASHMATCH) ||
759
- tok(PREFIXMATCH) ||
760
- tok(SUFFIXMATCH) ||
761
- tok(SUBSTRINGMATCH)
869
+ op = tok(/=/) ||
870
+ tok(INCLUDES) ||
871
+ tok(DASHMATCH) ||
872
+ tok(PREFIXMATCH) ||
873
+ tok(SUFFIXMATCH) ||
874
+ tok(SUBSTRINGMATCH)
875
+ if op
762
876
  @expected = "identifier or string"
763
877
  ss
764
878
  val = interp_ident || expr!(:interp_string)
@@ -771,7 +885,7 @@ module Sass
771
885
  end
772
886
 
773
887
  def attrib_name!
774
- if name_or_ns = interp_ident
888
+ if (name_or_ns = interp_ident)
775
889
  # E, E|E
776
890
  if tok(/\|(?!=)/)
777
891
  ns = name_or_ns
@@ -789,14 +903,15 @@ module Sass
789
903
  end
790
904
 
791
905
  def pseudo
792
- return unless s = tok(/::?/)
906
+ s = tok(/::?/)
907
+ return unless s
793
908
  @expected = "pseudoclass or pseudoelement"
794
909
  name = expr!(:interp_ident)
795
910
  if tok(/\(/)
796
911
  ss
797
912
  arg = expr!(:pseudo_arg)
798
913
  while tok(/,/)
799
- arg << ',' << str{ss}
914
+ arg << ',' << str {ss}
800
915
  arg.concat expr!(:pseudo_arg)
801
916
  end
802
917
  tok!(/\)/)
@@ -827,40 +942,50 @@ module Sass
827
942
  return sel if sel
828
943
  rethrow pseudo_err if pseudo_err
829
944
  rethrow sel_err if sel_err
830
- return
945
+ nil
946
+ end
947
+
948
+ def pseudo_expr_token
949
+ tok(PLUS) || tok(/[-*]/) || tok(NUMBER) || interp_string || tok(IDENT) || interpolation
831
950
  end
832
951
 
833
952
  def pseudo_expr
834
- return unless e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
835
- interp_string || tok(IDENT) || interpolation
836
- res = [e, str{ss}]
837
- while e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
838
- interp_string || tok(IDENT) || interpolation
839
- res << e << str{ss}
953
+ e = pseudo_expr_token
954
+ return unless e
955
+ res = [e, str {ss}]
956
+ while (e = pseudo_expr_token)
957
+ res << e << str {ss}
840
958
  end
841
959
  res
842
960
  end
843
961
 
844
962
  def declaration
845
963
  # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
846
- if s = tok(/[:\*\.]|\#(?!\{)/)
964
+ name_start_pos = source_position
965
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
847
966
  @use_property_exception = s !~ /[\.\#]/
848
- name = [s, str{ss}, *expr!(:interp_ident)]
967
+ name = [s, str {ss}, *expr!(:interp_ident)]
849
968
  else
850
- return unless name = interp_ident
969
+ name = interp_ident
970
+ return unless name
851
971
  name = [name] if name.is_a?(String)
852
972
  end
853
- if comment = tok(COMMENT)
973
+ if (comment = tok(COMMENT))
854
974
  name << comment
855
975
  end
976
+ name_end_pos = source_position
856
977
  ss
857
978
 
858
979
  tok!(/:/)
859
- space, value = value!
980
+ value_start_pos, space, value = value!
981
+ value_end_pos = source_position
860
982
  ss
861
983
  require_block = tok?(/\{/)
862
984
 
863
- node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
985
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
986
+ name_start_pos, value_end_pos)
987
+ node.name_source_range = range(name_start_pos, name_end_pos)
988
+ node.value_source_range = range(value_start_pos, value_end_pos)
864
989
 
865
990
  return node unless require_block
866
991
  nested_properties! node, space
@@ -868,18 +993,29 @@ module Sass
868
993
 
869
994
  def value!
870
995
  space = !str {ss}.empty?
996
+ value_start_pos = source_position
871
997
  @use_property_exception ||= space || !tok?(IDENT)
872
998
 
873
- return true, Sass::Script::String.new("") if tok?(/\{/)
999
+ if tok?(/\{/)
1000
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
1001
+ str.line = source_position.line
1002
+ str.source_range = range(source_position)
1003
+ return value_start_pos, true, str
1004
+ end
1005
+
1006
+ start_pos = source_position
874
1007
  # This is a bit of a dirty trick:
875
1008
  # if the value is completely static,
876
1009
  # we don't parse it at all, and instead return a plain old string
877
1010
  # containing the value.
878
1011
  # This results in a dramatic speed increase.
879
- if val = tok(STATIC_VALUE, true)
880
- return space, Sass::Script::String.new(val.strip)
1012
+ if (val = tok(STATIC_VALUE, true))
1013
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
1014
+ str.line = start_pos.line
1015
+ str.source_range = range(start_pos)
1016
+ return value_start_pos, space, str
881
1017
  end
882
- return space, sass_script(:parse)
1018
+ return value_start_pos, space, sass_script(:parse)
883
1019
  end
884
1020
 
885
1021
  def nested_properties!(node, space)
@@ -894,18 +1030,19 @@ MESSAGE
894
1030
  end
895
1031
 
896
1032
  def expr(allow_var = true)
897
- return unless t = term(allow_var)
898
- res = [t, str{ss}]
1033
+ t = term(allow_var)
1034
+ return unless t
1035
+ res = [t, str {ss}]
899
1036
 
900
1037
  while (o = operator) && (t = term(allow_var))
901
- res << o << t << str{ss}
1038
+ res << o << t << str {ss}
902
1039
  end
903
1040
 
904
1041
  res.flatten
905
1042
  end
906
1043
 
907
1044
  def term(allow_var)
908
- if e = tok(NUMBER) ||
1045
+ e = tok(NUMBER) ||
909
1046
  interp_uri ||
910
1047
  function(allow_var) ||
911
1048
  interp_string ||
@@ -913,29 +1050,30 @@ MESSAGE
913
1050
  interp_ident ||
914
1051
  tok(HEXCOLOR) ||
915
1052
  (allow_var && var_expr)
916
- return e
917
- end
1053
+ return e if e
918
1054
 
919
- return unless op = tok(/[+-]/)
1055
+ op = tok(/[+-]/)
1056
+ return unless op
920
1057
  @expected = "number or function"
921
- return [op, tok(NUMBER) || function(allow_var) ||
922
- (allow_var && var_expr) || expr!(:interpolation)]
1058
+ [op,
1059
+ tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)]
923
1060
  end
924
1061
 
925
1062
  def function(allow_var)
926
- return unless name = tok(FUNCTION)
1063
+ name = tok(FUNCTION)
1064
+ return unless name
927
1065
  if name == "expression(" || name == "calc("
928
1066
  str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
929
1067
  [name, str]
930
1068
  else
931
- [name, str{ss}, expr(allow_var), tok!(/\)/)]
1069
+ [name, str {ss}, expr(allow_var), tok!(/\)/)]
932
1070
  end
933
1071
  end
934
1072
 
935
1073
  def var_expr
936
1074
  return unless tok(/\$/)
937
1075
  line = @line
938
- var = Sass::Script::Variable.new(tok!(IDENT))
1076
+ var = Sass::Script::Tree::Variable.new(tok!(IDENT))
939
1077
  var.line = line
940
1078
  var
941
1079
  end
@@ -954,7 +1092,8 @@ MESSAGE
954
1092
  end
955
1093
 
956
1094
  def _interp_string(type)
957
- return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
1095
+ start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
1096
+ return unless start
958
1097
  res = [start]
959
1098
 
960
1099
  mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
@@ -968,17 +1107,20 @@ MESSAGE
968
1107
  end
969
1108
 
970
1109
  def interp_ident(start = IDENT)
971
- return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
1110
+ val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
1111
+ return unless val
972
1112
  res = [val]
973
- while val = tok(NAME) || interpolation
1113
+ while (val = tok(NAME) || interpolation)
974
1114
  res << val
975
1115
  end
976
1116
  res
977
1117
  end
978
1118
 
979
1119
  def interp_ident_or_var
980
- (id = interp_ident) and return id
981
- (var = var_expr) and return [var]
1120
+ id = interp_ident
1121
+ return id if id
1122
+ var = var_expr
1123
+ return [var] if var
982
1124
  end
983
1125
 
984
1126
  def interp_name
@@ -996,29 +1138,35 @@ MESSAGE
996
1138
  def str?
997
1139
  pos = @scanner.pos
998
1140
  line = @line
1141
+ offset = @offset
999
1142
  @strs.push ""
1000
1143
  throw_error {yield} && @strs.last
1001
1144
  rescue Sass::SyntaxError
1002
1145
  @scanner.pos = pos
1003
1146
  @line = line
1147
+ @offset = offset
1004
1148
  nil
1005
1149
  ensure
1006
1150
  @strs.pop
1007
1151
  end
1008
1152
 
1009
- def node(node)
1010
- node.line = @line
1153
+ def node(node, start_pos, end_pos = source_position)
1154
+ node.line = start_pos.line
1155
+ node.source_range = range(start_pos, end_pos)
1011
1156
  node
1012
1157
  end
1013
1158
 
1014
1159
  @sass_script_parser = Class.new(Sass::Script::Parser)
1015
1160
  @sass_script_parser.send(:include, ScriptParser)
1016
- # @private
1017
- def self.sass_script_parser; @sass_script_parser; end
1161
+
1162
+ class << self
1163
+ # @private
1164
+ attr_accessor :sass_script_parser
1165
+ end
1018
1166
 
1019
1167
  def sass_script(*args)
1020
- parser = self.class.sass_script_parser.new(@scanner, @line,
1021
- @scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
1168
+ parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
1169
+ :filename => @filename, :importer => @importer)
1022
1170
  result = parser.send(*args)
1023
1171
  unless @strs.empty?
1024
1172
  # Convert to CSS manually so that comments are ignored.
@@ -1026,6 +1174,7 @@ MESSAGE
1026
1174
  @strs.each {|s| s << src}
1027
1175
  end
1028
1176
  @line = parser.line
1177
+ @offset = parser.offset
1029
1178
  result
1030
1179
  rescue Sass::SyntaxError => e
1031
1180
  throw(:_sass_parser_error, true) if @throw_error
@@ -1040,12 +1189,15 @@ MESSAGE
1040
1189
  :media_query => "media query (e.g. print, screen, print and screen)",
1041
1190
  :media_query_list => "media query (e.g. print, screen, print and screen)",
1042
1191
  :media_expr => "media expression (e.g. (min-device-width: 800px))",
1192
+ :at_root_query => "@at-root query (e.g. (without: media))",
1193
+ :at_root_directive_list => '* or identifier',
1043
1194
  :pseudo_arg => "expression (e.g. fr, 2n+1)",
1044
1195
  :interp_ident => "identifier",
1045
1196
  :interp_name => "identifier",
1046
1197
  :qualified_name => "identifier",
1047
1198
  :expr => "expression (e.g. 1px, bold)",
1048
1199
  :_selector => "selector",
1200
+ :selector_comma_sequence => "selector",
1049
1201
  :simple_selector_sequence => "selector",
1050
1202
  :import_arg => "file to import (string or url())",
1051
1203
  :moz_document_function => "matching function (e.g. url-prefix(), domain())",
@@ -1053,21 +1205,27 @@ MESSAGE
1053
1205
  :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
1054
1206
  }
1055
1207
 
1056
- TOK_NAMES = Sass::Util.to_hash(
1057
- Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
1058
- merge(IDENT => "identifier", /[;}]/ => '";"')
1208
+ TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
1209
+ [Sass::SCSS::RX.const_get(c), c.downcase]
1210
+ end).merge(
1211
+ IDENT => "identifier",
1212
+ /[;}]/ => '";"',
1213
+ /\b(without|with)\b/ => '"with" or "without"'
1214
+ )
1059
1215
 
1060
1216
  def tok?(rx)
1061
1217
  @scanner.match?(rx)
1062
1218
  end
1063
1219
 
1064
1220
  def expr!(name)
1065
- (e = send(name)) && (return e)
1221
+ e = send(name)
1222
+ return e if e
1066
1223
  expected(EXPR_NAMES[name] || name.to_s)
1067
1224
  end
1068
1225
 
1069
1226
  def tok!(rx)
1070
- (t = tok(rx)) && (return t)
1227
+ t = tok(rx)
1228
+ return t if t
1071
1229
  name = TOK_NAMES[rx]
1072
1230
 
1073
1231
  unless name
@@ -1100,10 +1258,12 @@ MESSAGE
1100
1258
  old_throw_error, @throw_error = @throw_error, true
1101
1259
  pos = @scanner.pos
1102
1260
  line = @line
1261
+ offset = @offset
1103
1262
  expected = @expected
1104
1263
  if catch(:_sass_parser_error) {yield; false}
1105
1264
  @scanner.pos = pos
1106
1265
  @line = line
1266
+ @offset = offset
1107
1267
  @expected = expected
1108
1268
  {:pos => pos, :line => line, :expected => @expected, :block => block}
1109
1269
  end
@@ -1112,7 +1272,7 @@ MESSAGE
1112
1272
  end
1113
1273
 
1114
1274
  def rethrow(err)
1115
- if @throw_err
1275
+ if @throw_error
1116
1276
  throw :_sass_parser_error, err
1117
1277
  else
1118
1278
  @scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string)
@@ -1166,7 +1326,15 @@ MESSAGE
1166
1326
  @scanner.pos -= @scanner[-1].length
1167
1327
  res.slice!(-@scanner[-1].length..-1)
1168
1328
  end
1169
- @line += res.count(NEWLINE)
1329
+
1330
+ newline_count = res.count(NEWLINE)
1331
+ if newline_count > 0
1332
+ @line += newline_count
1333
+ @offset = res[res.rindex(NEWLINE)..-1].size
1334
+ else
1335
+ @offset += res.size
1336
+ end
1337
+
1170
1338
  @expected = nil
1171
1339
  if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
1172
1340
  @strs.each {|s| s << res}