sass 3.3.0 → 3.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/CONTRIBUTING.md +148 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +76 -62
  7. data/Rakefile +104 -24
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/VERSION_NAME +1 -1
  11. data/bin/sass +1 -1
  12. data/bin/scss +1 -1
  13. data/extra/sass-spec-ref.sh +32 -0
  14. data/extra/update_watch.rb +1 -1
  15. data/lib/sass/cache_stores/filesystem.rb +9 -5
  16. data/lib/sass/cache_stores/memory.rb +4 -5
  17. data/lib/sass/callbacks.rb +2 -2
  18. data/lib/sass/css.rb +12 -13
  19. data/lib/sass/deprecation.rb +55 -0
  20. data/lib/sass/engine.rb +106 -70
  21. data/lib/sass/environment.rb +39 -19
  22. data/lib/sass/error.rb +17 -20
  23. data/lib/sass/exec/base.rb +199 -0
  24. data/lib/sass/exec/sass_convert.rb +283 -0
  25. data/lib/sass/exec/sass_scss.rb +440 -0
  26. data/lib/sass/exec.rb +5 -771
  27. data/lib/sass/features.rb +9 -2
  28. data/lib/sass/importers/base.rb +8 -3
  29. data/lib/sass/importers/filesystem.rb +30 -38
  30. data/lib/sass/logger/base.rb +8 -2
  31. data/lib/sass/logger/delayed.rb +50 -0
  32. data/lib/sass/logger.rb +8 -3
  33. data/lib/sass/media.rb +1 -4
  34. data/lib/sass/plugin/compiler.rb +224 -90
  35. data/lib/sass/plugin/configuration.rb +38 -22
  36. data/lib/sass/plugin/merb.rb +2 -2
  37. data/lib/sass/plugin/rack.rb +3 -3
  38. data/lib/sass/plugin/rails.rb +1 -1
  39. data/lib/sass/plugin/staleness_checker.rb +4 -4
  40. data/lib/sass/plugin.rb +6 -5
  41. data/lib/sass/script/css_lexer.rb +1 -1
  42. data/lib/sass/script/css_parser.rb +2 -3
  43. data/lib/sass/script/css_variable_warning.rb +52 -0
  44. data/lib/sass/script/functions.rb +739 -318
  45. data/lib/sass/script/lexer.rb +134 -54
  46. data/lib/sass/script/parser.rb +252 -56
  47. data/lib/sass/script/tree/funcall.rb +13 -6
  48. data/lib/sass/script/tree/interpolation.rb +127 -4
  49. data/lib/sass/script/tree/list_literal.rb +31 -4
  50. data/lib/sass/script/tree/literal.rb +4 -0
  51. data/lib/sass/script/tree/node.rb +21 -3
  52. data/lib/sass/script/tree/operation.rb +54 -1
  53. data/lib/sass/script/tree/selector.rb +26 -0
  54. data/lib/sass/script/tree/string_interpolation.rb +59 -38
  55. data/lib/sass/script/tree/variable.rb +1 -1
  56. data/lib/sass/script/tree.rb +1 -0
  57. data/lib/sass/script/value/base.rb +17 -14
  58. data/lib/sass/script/value/bool.rb +0 -5
  59. data/lib/sass/script/value/color.rb +78 -42
  60. data/lib/sass/script/value/helpers.rb +119 -2
  61. data/lib/sass/script/value/list.rb +0 -15
  62. data/lib/sass/script/value/map.rb +1 -1
  63. data/lib/sass/script/value/null.rb +0 -5
  64. data/lib/sass/script/value/number.rb +112 -31
  65. data/lib/sass/script/value/string.rb +102 -13
  66. data/lib/sass/script/value.rb +0 -1
  67. data/lib/sass/script.rb +3 -3
  68. data/lib/sass/scss/css_parser.rb +24 -4
  69. data/lib/sass/scss/parser.rb +290 -383
  70. data/lib/sass/scss/rx.rb +17 -9
  71. data/lib/sass/scss/static_parser.rb +306 -4
  72. data/lib/sass/scss.rb +0 -2
  73. data/lib/sass/selector/abstract_sequence.rb +35 -18
  74. data/lib/sass/selector/comma_sequence.rb +114 -19
  75. data/lib/sass/selector/pseudo.rb +266 -0
  76. data/lib/sass/selector/sequence.rb +146 -40
  77. data/lib/sass/selector/simple.rb +22 -33
  78. data/lib/sass/selector/simple_sequence.rb +122 -39
  79. data/lib/sass/selector.rb +57 -197
  80. data/lib/sass/shared.rb +2 -2
  81. data/lib/sass/source/map.rb +31 -14
  82. data/lib/sass/source/position.rb +4 -4
  83. data/lib/sass/stack.rb +2 -8
  84. data/lib/sass/supports.rb +10 -13
  85. data/lib/sass/tree/at_root_node.rb +1 -0
  86. data/lib/sass/tree/charset_node.rb +1 -1
  87. data/lib/sass/tree/comment_node.rb +1 -1
  88. data/lib/sass/tree/css_import_node.rb +9 -1
  89. data/lib/sass/tree/directive_node.rb +8 -2
  90. data/lib/sass/tree/error_node.rb +18 -0
  91. data/lib/sass/tree/extend_node.rb +1 -1
  92. data/lib/sass/tree/function_node.rb +9 -0
  93. data/lib/sass/tree/import_node.rb +6 -5
  94. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  95. data/lib/sass/tree/node.rb +5 -3
  96. data/lib/sass/tree/prop_node.rb +6 -7
  97. data/lib/sass/tree/rule_node.rb +26 -11
  98. data/lib/sass/tree/visitors/check_nesting.rb +56 -32
  99. data/lib/sass/tree/visitors/convert.rb +59 -44
  100. data/lib/sass/tree/visitors/cssize.rb +34 -30
  101. data/lib/sass/tree/visitors/deep_copy.rb +6 -1
  102. data/lib/sass/tree/visitors/extend.rb +15 -13
  103. data/lib/sass/tree/visitors/perform.rb +87 -50
  104. data/lib/sass/tree/visitors/set_options.rb +15 -1
  105. data/lib/sass/tree/visitors/to_css.rb +72 -43
  106. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  107. data/lib/sass/util/normalized_map.rb +0 -1
  108. data/lib/sass/util/subset_map.rb +2 -3
  109. data/lib/sass/util.rb +334 -154
  110. data/lib/sass/version.rb +7 -7
  111. data/lib/sass.rb +10 -8
  112. data/test/sass/cache_test.rb +62 -20
  113. data/test/sass/callbacks_test.rb +1 -1
  114. data/test/sass/compiler_test.rb +24 -11
  115. data/test/sass/conversion_test.rb +241 -50
  116. data/test/sass/css2sass_test.rb +73 -5
  117. data/test/sass/css_variable_test.rb +132 -0
  118. data/test/sass/encoding_test.rb +219 -0
  119. data/test/sass/engine_test.rb +343 -260
  120. data/test/sass/exec_test.rb +12 -2
  121. data/test/sass/extend_test.rb +333 -44
  122. data/test/sass/functions_test.rb +353 -260
  123. data/test/sass/importer_test.rb +40 -21
  124. data/test/sass/logger_test.rb +1 -1
  125. data/test/sass/more_results/more_import.css +1 -1
  126. data/test/sass/more_templates/more1.sass +10 -10
  127. data/test/sass/more_templates/more_import.sass +2 -2
  128. data/test/sass/plugin_test.rb +24 -21
  129. data/test/sass/results/compact.css +1 -1
  130. data/test/sass/results/complex.css +4 -4
  131. data/test/sass/results/expanded.css +1 -1
  132. data/test/sass/results/import.css +1 -1
  133. data/test/sass/results/import_charset_ibm866.css +2 -2
  134. data/test/sass/results/mixins.css +17 -17
  135. data/test/sass/results/nested.css +1 -1
  136. data/test/sass/results/parent_ref.css +2 -2
  137. data/test/sass/results/script.css +5 -5
  138. data/test/sass/results/scss_import.css +1 -1
  139. data/test/sass/script_conversion_test.rb +71 -39
  140. data/test/sass/script_test.rb +714 -123
  141. data/test/sass/scss/css_test.rb +213 -30
  142. data/test/sass/scss/rx_test.rb +8 -4
  143. data/test/sass/scss/scss_test.rb +766 -22
  144. data/test/sass/source_map_test.rb +263 -95
  145. data/test/sass/superselector_test.rb +210 -0
  146. data/test/sass/templates/_partial.sass +1 -1
  147. data/test/sass/templates/basic.sass +10 -10
  148. data/test/sass/templates/bork1.sass +1 -1
  149. data/test/sass/templates/bork5.sass +1 -1
  150. data/test/sass/templates/compact.sass +10 -10
  151. data/test/sass/templates/complex.sass +187 -187
  152. data/test/sass/templates/compressed.sass +10 -10
  153. data/test/sass/templates/expanded.sass +10 -10
  154. data/test/sass/templates/import.sass +2 -2
  155. data/test/sass/templates/importee.sass +3 -3
  156. data/test/sass/templates/mixins.sass +22 -22
  157. data/test/sass/templates/multiline.sass +4 -4
  158. data/test/sass/templates/nested.sass +13 -13
  159. data/test/sass/templates/parent_ref.sass +12 -12
  160. data/test/sass/templates/script.sass +70 -70
  161. data/test/sass/templates/scss_import.scss +2 -1
  162. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
  163. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
  164. data/test/sass/templates/subdir/subdir.sass +3 -3
  165. data/test/sass/templates/units.sass +10 -10
  166. data/test/sass/test_helper.rb +1 -1
  167. data/test/sass/util/multibyte_string_scanner_test.rb +11 -3
  168. data/test/sass/util/normalized_map_test.rb +1 -1
  169. data/test/sass/util/subset_map_test.rb +2 -2
  170. data/test/sass/util_test.rb +46 -45
  171. data/test/sass/value_helpers_test.rb +5 -7
  172. data/test/sass-spec.yml +3 -0
  173. data/test/test_helper.rb +7 -6
  174. data/vendor/listen/CHANGELOG.md +1 -228
  175. data/vendor/listen/Gemfile +5 -15
  176. data/vendor/listen/README.md +111 -77
  177. data/vendor/listen/Rakefile +0 -42
  178. data/vendor/listen/lib/listen/adapter.rb +195 -82
  179. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  180. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  181. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  182. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  183. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  184. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  185. data/vendor/listen/lib/listen/listener.rb +135 -37
  186. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  187. data/vendor/listen/lib/listen/version.rb +1 -1
  188. data/vendor/listen/lib/listen.rb +33 -19
  189. data/vendor/listen/listen.gemspec +6 -0
  190. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  191. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  192. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  193. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  194. data/vendor/listen/spec/listen_spec.rb +15 -21
  195. data/vendor/listen/spec/spec_helper.rb +4 -0
  196. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  197. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  198. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  199. metadata +310 -300
  200. data/CONTRIBUTING +0 -3
  201. data/ext/mkrf_conf.rb +0 -27
  202. data/lib/sass/script/value/deprecated_false.rb +0 -55
  203. data/lib/sass/scss/script_lexer.rb +0 -15
  204. data/lib/sass/scss/script_parser.rb +0 -25
  205. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  206. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  207. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  208. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'set'
2
3
 
3
4
  module Sass
@@ -15,21 +16,20 @@ module Sass
15
16
  # warnings and source maps.
16
17
  # @param importer [Sass::Importers::Base] The importer used to import the
17
18
  # file being parsed. Used for source maps.
18
- # @param line [Fixnum] The 1-based line on which the source string appeared,
19
+ # @param line [Integer] The 1-based line on which the source string appeared,
19
20
  # if it's part of another document.
20
- # @param offset [Fixnum] The 1-based character (not byte) offset in the line on
21
+ # @param offset [Integer] The 1-based character (not byte) offset in the line on
21
22
  # which the source string starts. Used for error reporting and sourcemap
22
23
  # building.
23
- # @comment
24
- # rubocop:disable ParameterLists
25
24
  def initialize(str, filename, importer, line = 1, offset = 1)
26
- # rubocop:enable ParameterLists
27
25
  @template = str
28
26
  @filename = filename
29
27
  @importer = importer
30
28
  @line = line
31
29
  @offset = offset
32
30
  @strs = []
31
+ @expected = nil
32
+ @throw_error = false
33
33
  end
34
34
 
35
35
  # Parses an SCSS document.
@@ -39,7 +39,7 @@ module Sass
39
39
  def parse
40
40
  init_scanner!
41
41
  root = stylesheet
42
- expected("selector or at-rule") unless @scanner.eos?
42
+ expected("selector or at-rule") unless root && @scanner.eos?
43
43
  root
44
44
  end
45
45
 
@@ -54,6 +54,15 @@ module Sass
54
54
  interp_ident
55
55
  end
56
56
 
57
+ # Parses a supports clause for an @import directive
58
+ def parse_supports_clause
59
+ init_scanner!
60
+ ss
61
+ clause = supports_clause
62
+ ss
63
+ clause
64
+ end
65
+
57
66
  # Parses a media query list.
58
67
  #
59
68
  # @return [Sass::Media::QueryList] The parsed query list
@@ -62,7 +71,7 @@ module Sass
62
71
  def parse_media_query_list
63
72
  init_scanner!
64
73
  ql = media_query_list
65
- expected("media query list") unless @scanner.eos?
74
+ expected("media query list") unless ql && @scanner.eos?
66
75
  ql
67
76
  end
68
77
 
@@ -74,7 +83,7 @@ module Sass
74
83
  def parse_at_root_query
75
84
  init_scanner!
76
85
  query = at_root_query
77
- expected("@at-root query list") unless @scanner.eos?
86
+ expected("@at-root query list") unless query && @scanner.eos?
78
87
  query
79
88
  end
80
89
 
@@ -86,7 +95,7 @@ module Sass
86
95
  def parse_supports_condition
87
96
  init_scanner!
88
97
  condition = supports_condition
89
- expected("supports condition") unless @scanner.eos?
98
+ expected("supports condition") unless condition && @scanner.eos?
90
99
  condition
91
100
  end
92
101
 
@@ -107,7 +116,7 @@ module Sass
107
116
  if @template.is_a?(StringScanner)
108
117
  @template
109
118
  else
110
- Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", ""))
119
+ Sass::Util::MultibyteStringScanner.new(@template.tr("\r", ""))
111
120
  end
112
121
  end
113
122
 
@@ -149,16 +158,16 @@ module Sass
149
158
  silent = text =~ %r{\A//}
150
159
  loud = !silent && text =~ %r{\A/[/*]!}
151
160
  line = @line - text.count("\n")
161
+ comment_start = @scanner.pos - text.length
162
+ index_before_line = @scanner.string.rindex("\n", comment_start) || -1
163
+ offset = comment_start - index_before_line
152
164
 
153
165
  if silent
154
166
  value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
155
167
  else
156
- value = Sass::Engine.parse_interp(
157
- text, line, @scanner.pos - text.size, :filename => @filename)
158
- value.unshift(@scanner.
159
- string[0...@scanner.pos].
160
- reverse[/.*?\*\/(.*?)($|\Z)/, 1].
161
- reverse.gsub(/[^\s]/, ' '))
168
+ value = Sass::Engine.parse_interp(text, line, offset, :filename => @filename)
169
+ line_before_comment = @scanner.string[index_before_line + 1...comment_start]
170
+ value.unshift(line_before_comment.gsub(/[^\s]/, ' '))
162
171
  end
163
172
 
164
173
  type = if silent
@@ -168,14 +177,14 @@ module Sass
168
177
  else
169
178
  :normal
170
179
  end
171
- comment = Sass::Tree::CommentNode.new(value, type)
172
- comment.line = line
180
+ start_pos = Sass::Source::Position.new(line, offset)
181
+ comment = node(Sass::Tree::CommentNode.new(value, type), start_pos)
173
182
  node << comment
174
183
  end
175
184
 
176
185
  DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
177
186
  :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
178
- :_moz_document, :at_root]
187
+ :_moz_document, :at_root, :error]
179
188
 
180
189
  PREFIXED_DIRECTIVES = Set[:supports]
181
190
 
@@ -191,10 +200,7 @@ module Sass
191
200
  return dir
192
201
  end
193
202
 
194
- # Most at-rules take expressions (e.g. @import),
195
- # but some (e.g. @page) take selector-like arguments.
196
- # Some take no arguments at all.
197
- val = expr || selector
203
+ val = almost_any_value
198
204
  val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
199
205
  directive_body(val, start_pos)
200
206
  end
@@ -212,12 +218,12 @@ module Sass
212
218
  end
213
219
 
214
220
  def special_directive(name, start_pos)
215
- sym = name.gsub('-', '_').to_sym
221
+ sym = name.tr('-', '_').to_sym
216
222
  DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
217
223
  end
218
224
 
219
225
  def prefixed_directive(name, start_pos)
220
- sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
226
+ sym = deprefix(name).tr('-', '_').to_sym
221
227
  PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
222
228
  end
223
229
 
@@ -350,10 +356,12 @@ module Sass
350
356
  end
351
357
 
352
358
  def extend_directive(start_pos)
353
- selector, selector_range = expr!(:selector_sequence)
359
+ selector_start_pos = source_position
360
+ @expected = "selector"
361
+ selector = Sass::Util.strip_string_array(expr!(:almost_any_value))
354
362
  optional = tok(OPTIONAL)
355
363
  ss
356
- node(Sass::Tree::ExtendNode.new(selector, !!optional, selector_range), start_pos)
364
+ node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos)
357
365
  end
358
366
 
359
367
  def import_directive(start_pos)
@@ -371,24 +379,28 @@ module Sass
371
379
 
372
380
  def import_arg
373
381
  start_pos = source_position
374
- return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
382
+ return unless (str = string) || (uri = tok?(/url\(/i))
375
383
  if uri
376
384
  str = sass_script(:parse_string)
377
385
  ss
386
+ supports = supports_clause
387
+ ss
378
388
  media = media_query_list
379
389
  ss
380
- return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
390
+ return node(Tree::CssImportNode.new(str, media.to_a, supports), start_pos)
381
391
  end
382
-
383
- path = @scanner[1] || @scanner[2]
384
392
  ss
385
393
 
394
+ supports = supports_clause
395
+ ss
386
396
  media = media_query_list
387
- if path =~ %r{^(https?:)?//} || media || use_css_import?
388
- return node(Sass::Tree::CssImportNode.new(str, media.to_a), start_pos)
397
+ if str =~ %r{^(https?:)?//} || media || supports || use_css_import?
398
+ return node(
399
+ Sass::Tree::CssImportNode.new(
400
+ Sass::Script::Value::String.quote(str), media.to_a, supports), start_pos)
389
401
  end
390
402
 
391
- node(Sass::Tree::ImportNode.new(path.strip), start_pos)
403
+ node(Sass::Tree::ImportNode.new(str.strip), start_pos)
392
404
  end
393
405
 
394
406
  def use_css_import?; false; end
@@ -471,8 +483,7 @@ module Sass
471
483
  alias_method :at_root_query, :query_expr
472
484
 
473
485
  def charset_directive(start_pos)
474
- tok! STRING
475
- name = @scanner[1] || @scanner[2]
486
+ name = expr!(:string)
476
487
  ss
477
488
  node(Sass::Tree::CharsetNode.new(name), start_pos)
478
489
  end
@@ -500,7 +511,7 @@ module Sass
500
511
 
501
512
  def moz_document_function
502
513
  val = interp_uri || _interp_string(:url_prefix) ||
503
- _interp_string(:domain) || function(!:allow_var) || interpolation
514
+ _interp_string(:domain) || function(false) || interpolation
504
515
  return unless val
505
516
  ss
506
517
  val
@@ -529,6 +540,10 @@ module Sass
529
540
  arr
530
541
  end
531
542
 
543
+ def error_directive(start_pos)
544
+ node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos)
545
+ end
546
+
532
547
  # http://www.w3.org/TR/css3-conditional/
533
548
  def supports_directive(name, start_pos)
534
549
  condition = expr!(:supports_condition)
@@ -542,10 +557,23 @@ module Sass
542
557
  node(node, start_pos)
543
558
  end
544
559
 
560
+ def supports_clause
561
+ return unless tok(/supports\(/i)
562
+ ss
563
+ supports = import_supports_condition
564
+ ss
565
+ tok!(/\)/)
566
+ supports
567
+ end
568
+
545
569
  def supports_condition
546
570
  supports_negation || supports_operator || supports_interpolation
547
571
  end
548
572
 
573
+ def import_supports_condition
574
+ supports_condition || supports_declaration
575
+ end
576
+
549
577
  def supports_negation
550
578
  return unless tok(/not/i)
551
579
  ss
@@ -555,7 +583,9 @@ module Sass
555
583
  def supports_operator
556
584
  cond = supports_condition_in_parens
557
585
  return unless cond
558
- while (op = tok(/and|or/i))
586
+ re = /and|or/i
587
+ while (op = tok(re))
588
+ re = /#{op}/i
559
589
  ss
560
590
  cond = Sass::Supports::Operator.new(
561
591
  cond, expr!(:supports_condition_in_parens), op)
@@ -563,6 +593,13 @@ module Sass
563
593
  cond
564
594
  end
565
595
 
596
+ def supports_declaration
597
+ name = sass_script(:parse)
598
+ tok!(/:/); ss
599
+ value = sass_script(:parse)
600
+ Sass::Supports::Declaration.new(name, value)
601
+ end
602
+
566
603
  def supports_condition_in_parens
567
604
  interp = supports_interpolation
568
605
  return interp if interp
@@ -571,19 +608,12 @@ module Sass
571
608
  tok!(/\)/); ss
572
609
  cond
573
610
  else
574
- name = sass_script(:parse)
575
- tok!(/:/); ss
576
- value = sass_script(:parse)
611
+ decl = supports_declaration
577
612
  tok!(/\)/); ss
578
- Sass::Supports::Declaration.new(name, value)
613
+ decl
579
614
  end
580
615
  end
581
616
 
582
- def supports_declaration_condition
583
- return unless tok(/\(/); ss
584
- supports_declaration_body
585
- end
586
-
587
617
  def supports_interpolation
588
618
  interp = interpolation
589
619
  return unless interp
@@ -619,15 +649,15 @@ module Sass
619
649
  # are disallowed by the CSS spec,
620
650
  # but they're included here for compatibility
621
651
  # with some proprietary MS properties
622
- str {ss if tok(/[\/,:.=]/)}
652
+ str {ss if tok(%r{[/,:.=]})}
623
653
  end
624
654
 
625
655
  def ruleset
626
656
  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)
657
+ return unless (rules = almost_any_value)
658
+ block(
659
+ node(
660
+ Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset)
631
661
  end
632
662
 
633
663
  def block(node, context)
@@ -661,315 +691,177 @@ module Sass
661
691
  child_or_array.has_children
662
692
  end
663
693
 
664
- # This is a nasty hack, and the only place in the parser
665
- # that requires a large amount of backtracking.
666
- # The reason is that we can't figure out if certain strings
667
- # are declarations or rulesets with fixed finite lookahead.
668
- # For example, "foo:bar baz baz baz..." could be either a property
669
- # or a selector.
694
+ # When parsing the contents of a ruleset, it can be difficult to tell
695
+ # declarations apart from nested rulesets. Since we don't thoroughly parse
696
+ # selectors until after resolving interpolation, we can share a bunch of
697
+ # the parsing of the two, but we need to disambiguate them first. We use
698
+ # the following criteria:
699
+ #
700
+ # * If the entity doesn't start with an identifier followed by a colon,
701
+ # it's a selector. There are some additional mostly-unimportant cases
702
+ # here to support various declaration hacks.
703
+ #
704
+ # * If the colon is followed by another colon, it's a selector.
670
705
  #
671
- # To handle this, we simply check if it works as a property
672
- # (which is the most common case)
673
- # and, if it doesn't, try it as a ruleset.
706
+ # * Otherwise, if the colon is followed by anything other than
707
+ # interpolation or a character that's valid as the beginning of an
708
+ # identifier, it's a declaration.
674
709
  #
675
- # We could eke some more efficiency out of this
676
- # by handling some easy cases (first token isn't an identifier,
677
- # no colon after the identifier, whitespace after the colon),
678
- # but I'm not sure the gains would be worth the added complexity.
710
+ # * If the colon is followed by interpolation or a valid identifier, try
711
+ # parsing it as a declaration value. If this fails, backtrack and parse
712
+ # it as a selector.
713
+ #
714
+ # * If the declaration value value valid but is followed by "{", backtrack
715
+ # and parse it as a selector anyway. This ensures that ".foo:bar {" is
716
+ # always parsed as a selector and never as a property with nested
717
+ # properties beneath it.
679
718
  def declaration_or_ruleset
680
- old_use_property_exception, @use_property_exception =
681
- @use_property_exception, false
682
- decl_err = catch_error do
683
- decl = declaration
684
- unless decl && decl.has_children
685
- # We want an exception if it's not there,
686
- # but we don't want to consume if it is
687
- tok!(/[;}]/) unless tok?(/[;}]/)
688
- end
689
- return decl
690
- end
691
-
692
- ruleset_err = catch_error {return ruleset}
693
- rethrow(@use_property_exception ? decl_err : ruleset_err)
694
- ensure
695
- @use_property_exception = old_use_property_exception
696
- end
697
-
698
- def selector_sequence
699
719
  start_pos = source_position
700
- if (sel = tok(STATIC_SELECTOR, true))
701
- return [sel], range(start_pos)
702
- end
703
-
704
- rules = []
705
- v = selector
706
- return unless v
707
- rules.concat v
708
-
709
- ws = ''
710
- while tok(/,/)
711
- ws << str {ss}
712
- if (v = selector)
713
- rules << ',' << ws
714
- rules.concat v
715
- ws = ''
716
- end
717
- end
718
- return rules, range(start_pos)
719
- end
720
-
721
- def selector
722
- sel = _selector
723
- return unless sel
724
- sel.to_a
725
- end
720
+ declaration = try_declaration
726
721
 
727
- def selector_comma_sequence
728
- sel = _selector
729
- return unless sel
730
- selectors = [sel]
731
- ws = ''
732
- while tok(/,/)
733
- ws << str {ss}
734
- if (sel = _selector)
735
- selectors << sel
736
- if ws.include?("\n")
737
- selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members)
738
- end
739
- ws = ''
740
- end
722
+ if declaration.nil?
723
+ return unless (selector = almost_any_value)
724
+ elsif declaration.is_a?(Array)
725
+ selector = declaration
726
+ else
727
+ # Declaration should be a PropNode.
728
+ return declaration
741
729
  end
742
- Selector::CommaSequence.new(selectors)
743
- end
744
-
745
- def _selector
746
- # The combinator here allows the "> E" hack
747
- val = combinator || simple_selector_sequence
748
- return unless val
749
- nl = str {ss}.include?("\n")
750
- res = []
751
- res << val
752
- res << "\n" if nl
753
730
 
754
- while (val = combinator || simple_selector_sequence)
755
- res << val
756
- res << "\n" if str {ss}.include?("\n")
731
+ if (additional_selector = almost_any_value)
732
+ selector << additional_selector
757
733
  end
758
- Selector::Sequence.new(res.compact)
759
- end
760
734
 
761
- def combinator
762
- tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
735
+ block(
736
+ node(
737
+ Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset)
763
738
  end
764
739
 
765
- def reference_combinator
766
- return unless tok(/\//)
767
- res = ['/']
768
- ns, name = expr!(:qualified_name)
769
- res << ns << '|' if ns
770
- res << name << tok!(/\//)
771
- res = res.flatten
772
- res = res.join '' if res.all? {|e| e.is_a?(String)}
773
- res
774
- end
775
-
776
- def simple_selector_sequence
777
- # Returning expr by default allows for stuff like
778
- # http://www.w3.org/TR/css3-animations/#keyframes-
779
-
780
- start_pos = source_position
781
- e = element_name || id_selector ||
782
- class_selector || placeholder_selector || attrib || pseudo ||
783
- parent_selector || interpolation_selector
784
- return expr(!:allow_var) unless e
785
- res = [e]
786
-
787
- # The tok(/\*/) allows the "E*" hack
788
- while (v = id_selector || class_selector || placeholder_selector ||
789
- attrib || pseudo || interpolation_selector ||
790
- (tok(/\*/) && Selector::Universal.new(nil)))
791
- res << v
740
+ # Tries to parse a declaration, and returns the value parsed so far if it
741
+ # fails.
742
+ #
743
+ # This has three possible return types. It can return `nil`, indicating
744
+ # that parsing failed completely and the scanner hasn't moved forward at
745
+ # all. It can return an Array, indicating that parsing failed after
746
+ # consuming some text (possibly containing interpolation), which is
747
+ # returned. Or it can return a PropNode, indicating that parsing
748
+ # succeeded.
749
+ def try_declaration
750
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
751
+ # val" hacks.
752
+ name_start_pos = source_position
753
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
754
+ name = [s, str {ss}]
755
+ return name unless (ident = interp_ident)
756
+ name << ident
757
+ else
758
+ return unless (name = interp_ident)
759
+ name = Array(name)
792
760
  end
793
761
 
794
- pos = @scanner.pos
795
- line = @line
796
- if (sel = str? {simple_selector_sequence})
797
- @scanner.pos = pos
798
- @line = line
799
- begin
800
- # If we see "*E", don't force a throw because this could be the
801
- # "*prop: val" hack.
802
- expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
803
- throw_error {expected('"{"')}
804
- rescue Sass::SyntaxError => e
805
- e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector."
806
- raise e
807
- end
762
+ if (comment = tok(COMMENT))
763
+ name << comment
808
764
  end
765
+ name_end_pos = source_position
809
766
 
810
- Selector::SimpleSequence.new(res, tok(/!/), range(start_pos))
811
- end
812
-
813
- def parent_selector
814
- return unless tok(/&/)
815
- Selector::Parent.new(interp_ident(NAME) || [])
816
- end
817
-
818
- def class_selector
819
- return unless tok(/\./)
820
- @expected = "class name"
821
- Selector::Class.new(merge(expr!(:interp_ident)))
822
- end
823
-
824
- def id_selector
825
- return unless tok(/#(?!\{)/)
826
- @expected = "id name"
827
- Selector::Id.new(merge(expr!(:interp_name)))
828
- end
829
-
830
- def placeholder_selector
831
- return unless tok(/%/)
832
- @expected = "placeholder name"
833
- Selector::Placeholder.new(merge(expr!(:interp_ident)))
834
- end
835
-
836
- def element_name
837
- ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
838
- return unless ns || name
767
+ mid = [str {ss}]
768
+ return name + mid unless tok(/:/)
769
+ mid << ':'
770
+ return name + mid + [':'] if tok(/:/)
771
+ mid << str {ss}
772
+ post_colon_whitespace = !mid.last.empty?
773
+ could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START))
839
774
 
840
- if name == '*'
841
- Selector::Universal.new(merge(ns))
842
- else
843
- Selector::Element.new(merge(name), merge(ns))
775
+ value_start_pos = source_position
776
+ value = nil
777
+ error = catch_error do
778
+ value = value!(name.first.is_a?(String) && name.first.start_with?("--"))
779
+ if tok?(/\{/)
780
+ # Properties that are ambiguous with selectors can't have additional
781
+ # properties nested beneath them.
782
+ tok!(/;/) if could_be_selector
783
+ elsif !tok?(/[;{}]/)
784
+ # We want an exception if there's no valid end-of-property character
785
+ # exists, but we don't want to consume it if it does.
786
+ tok!(/[;{}]/)
787
+ end
844
788
  end
845
- end
846
789
 
847
- def qualified_name(allow_star_name = false)
848
- name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
849
- return unless name
850
- return nil, name unless tok(/\|/)
790
+ if error
791
+ rethrow error unless could_be_selector
851
792
 
852
- return name, expr!(:interp_ident) unless allow_star_name
853
- @expected = "identifier or *"
854
- return name, interp_ident || tok!(/\*/)
855
- end
793
+ # If the value would be followed by a semicolon, it's definitely
794
+ # supposed to be a property, not a selector.
795
+ additional_selector = almost_any_value
796
+ rethrow error if tok?(/;/)
856
797
 
857
- def interpolation_selector
858
- if (script = interpolation)
859
- Selector::Interpolation.new(script)
798
+ return name + mid + (additional_selector || [])
860
799
  end
861
- end
862
800
 
863
- def attrib
864
- return unless tok(/\[/)
865
- ss
866
- ns, name = attrib_name!
801
+ value_end_pos = source_position
867
802
  ss
803
+ require_block = tok?(/\{/)
868
804
 
869
- op = tok(/=/) ||
870
- tok(INCLUDES) ||
871
- tok(DASHMATCH) ||
872
- tok(PREFIXMATCH) ||
873
- tok(SUFFIXMATCH) ||
874
- tok(SUBSTRINGMATCH)
875
- if op
876
- @expected = "identifier or string"
877
- ss
878
- val = interp_ident || expr!(:interp_string)
879
- ss
880
- end
881
- flags = interp_ident || interp_string
882
- tok!(/\]/)
883
-
884
- Selector::Attribute.new(merge(name), merge(ns), op, merge(val), merge(flags))
885
- end
886
-
887
- def attrib_name!
888
- if (name_or_ns = interp_ident)
889
- # E, E|E
890
- if tok(/\|(?!=)/)
891
- ns = name_or_ns
892
- name = interp_ident
893
- else
894
- name = name_or_ns
895
- end
896
- else
897
- # *|E or |E
898
- ns = [tok(/\*/) || ""]
899
- tok!(/\|/)
900
- name = expr!(:interp_ident)
901
- end
902
- return ns, name
903
- end
904
-
905
- def pseudo
906
- s = tok(/::?/)
907
- return unless s
908
- @expected = "pseudoclass or pseudoelement"
909
- name = expr!(:interp_ident)
910
- if tok(/\(/)
911
- ss
912
- arg = expr!(:pseudo_arg)
913
- while tok(/,/)
914
- arg << ',' << str {ss}
915
- arg.concat expr!(:pseudo_arg)
916
- end
917
- tok!(/\)/)
918
- end
919
- Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
920
- end
921
-
922
- def pseudo_arg
923
- # In the CSS spec, every pseudo-class/element either takes a pseudo
924
- # expression or a selector comma sequence as an argument. However, we
925
- # don't want to have to know which takes which, so we handle both at
926
- # once.
927
- #
928
- # However, there are some ambiguities between the two. For instance, "n"
929
- # could start a pseudo expression like "n+1", or it could start a
930
- # selector like "n|m". In order to handle this, we must regrettably
931
- # backtrack.
932
- expr, sel = nil, nil
933
- pseudo_err = catch_error do
934
- expr = pseudo_expr
935
- next if tok?(/[,)]/)
936
- expr = nil
937
- expected '")"'
938
- end
939
-
940
- return expr if expr
941
- sel_err = catch_error {sel = selector}
942
- return sel if sel
943
- rethrow pseudo_err if pseudo_err
944
- rethrow sel_err if sel_err
945
- nil
946
- end
805
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
806
+ name_start_pos, value_end_pos)
807
+ node.name_source_range = range(name_start_pos, name_end_pos)
808
+ node.value_source_range = range(value_start_pos, value_end_pos)
947
809
 
948
- def pseudo_expr_token
949
- tok(PLUS) || tok(/[-*]/) || tok(NUMBER) || interp_string || tok(IDENT) || interpolation
810
+ return node unless require_block
811
+ nested_properties! node
950
812
  end
951
813
 
952
- def pseudo_expr
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}
814
+ # This production is similar to the CSS [`<any-value>`][any-value]
815
+ # production, but as the name implies, not quite the same. It's meant to
816
+ # consume values that could be a selector, an expression, or a combination
817
+ # of both. It respects strings and comments and supports interpolation. It
818
+ # will consume up to "{", "}", ";", or "!".
819
+ #
820
+ # [any-value]: http://dev.w3.org/csswg/css-variables/#typedef-any-value
821
+ #
822
+ # Values consumed by this production will usually be parsed more
823
+ # thoroughly once interpolation has been resolved.
824
+ def almost_any_value
825
+ return unless (tok = almost_any_value_token)
826
+ sel = [tok]
827
+ while (tok = almost_any_value_token)
828
+ sel << tok
958
829
  end
959
- res
830
+ merge(sel)
831
+ end
832
+
833
+ def almost_any_value_token
834
+ tok(%r{
835
+ (
836
+ \\.
837
+ |
838
+ (?!url\()
839
+ [^"'/\#!;\{\}] # "
840
+ |
841
+ # interp_uri will handle most url() calls, but not ones that take strings
842
+ url\(#{W}(?=")
843
+ |
844
+ /(?![/*])
845
+ |
846
+ \#(?!\{)
847
+ |
848
+ !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed.
849
+ )+
850
+ }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri ||
851
+ interpolation(:warn_for_color)
960
852
  end
961
853
 
962
854
  def declaration
963
- # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
855
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
856
+ # val" hacks.
964
857
  name_start_pos = source_position
965
858
  if (s = tok(/[:\*\.]|\#(?!\{)/))
966
- @use_property_exception = s !~ /[\.\#]/
967
859
  name = [s, str {ss}, *expr!(:interp_ident)]
968
860
  else
969
- name = interp_ident
970
- return unless name
971
- name = [name] if name.is_a?(String)
861
+ return unless (name = interp_ident)
862
+ name = Array(name)
972
863
  end
864
+
973
865
  if (comment = tok(COMMENT))
974
866
  name << comment
975
867
  end
@@ -977,7 +869,9 @@ module Sass
977
869
  ss
978
870
 
979
871
  tok!(/:/)
980
- value_start_pos, space, value = value!
872
+ ss
873
+ value_start_pos = source_position
874
+ value = value!(name.first.is_a?(String) && name.first.start_with?("--"))
981
875
  value_end_pos = source_position
982
876
  ss
983
877
  require_block = tok?(/\{/)
@@ -988,19 +882,15 @@ module Sass
988
882
  node.value_source_range = range(value_start_pos, value_end_pos)
989
883
 
990
884
  return node unless require_block
991
- nested_properties! node, space
885
+ nested_properties! node
992
886
  end
993
887
 
994
- def value!
995
- space = !str {ss}.empty?
996
- value_start_pos = source_position
997
- @use_property_exception ||= space || !tok?(IDENT)
998
-
888
+ def value!(css_variable = false)
999
889
  if tok?(/\{/)
1000
890
  str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
1001
891
  str.line = source_position.line
1002
892
  str.source_range = range(source_position)
1003
- return value_start_pos, true, str
893
+ return str
1004
894
  end
1005
895
 
1006
896
  start_pos = source_position
@@ -1013,18 +903,21 @@ module Sass
1013
903
  str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
1014
904
  str.line = start_pos.line
1015
905
  str.source_range = range(start_pos)
1016
- return value_start_pos, space, str
906
+ return str
1017
907
  end
1018
- return value_start_pos, space, sass_script(:parse)
908
+
909
+ sass_script(:parse, css_variable)
1019
910
  end
1020
911
 
1021
- def nested_properties!(node, space)
1022
- err(<<MESSAGE) unless space
1023
- Invalid CSS: a space is required between a property and its definition
1024
- when it has other properties nested beneath it.
1025
- MESSAGE
912
+ def nested_properties!(node)
913
+ if node.name.first.is_a?(String) && node.name.first.start_with?("--")
914
+ Sass::Util.sass_warn(<<WARNING)
915
+ DEPRECATION WARNING on line #{@line}#{" of #{@filename}" if @filename}:
916
+ Sass 3.6 will change the way CSS variables are parsed. Instead of being parsed as
917
+ normal properties, they will not allow any Sass-specific behavior other than \#{}.
918
+ WARNING
919
+ end
1026
920
 
1027
- @use_property_exception = true
1028
921
  @expected = 'expression (e.g. 1px, bold) or "{"'
1029
922
  block(node, :property)
1030
923
  end
@@ -1078,9 +971,14 @@ MESSAGE
1078
971
  var
1079
972
  end
1080
973
 
1081
- def interpolation
974
+ def interpolation(warn_for_color = false)
1082
975
  return unless tok(INTERP_START)
1083
- sass_script(:parse_interpolated)
976
+ sass_script(:parse_interpolated, warn_for_color)
977
+ end
978
+
979
+ def string
980
+ return unless tok(STRING)
981
+ Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
1084
982
  end
1085
983
 
1086
984
  def interp_string
@@ -1107,10 +1005,10 @@ MESSAGE
1107
1005
  end
1108
1006
 
1109
1007
  def interp_ident(start = IDENT)
1110
- val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
1008
+ val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP, true)
1111
1009
  return unless val
1112
1010
  res = [val]
1113
- while (val = tok(NAME) || interpolation)
1011
+ while (val = tok(NAME) || interpolation(:warn_for_color))
1114
1012
  res << val
1115
1013
  end
1116
1014
  res
@@ -1123,12 +1021,8 @@ MESSAGE
1123
1021
  return [var] if var
1124
1022
  end
1125
1023
 
1126
- def interp_name
1127
- interp_ident NAME
1128
- end
1129
-
1130
1024
  def str
1131
- @strs.push ""
1025
+ @strs.push String.new("")
1132
1026
  yield
1133
1027
  @strs.last
1134
1028
  ensure
@@ -1156,8 +1050,7 @@ MESSAGE
1156
1050
  node
1157
1051
  end
1158
1052
 
1159
- @sass_script_parser = Class.new(Sass::Script::Parser)
1160
- @sass_script_parser.send(:include, ScriptParser)
1053
+ @sass_script_parser = Sass::Script::Parser
1161
1054
 
1162
1055
  class << self
1163
1056
  # @private
@@ -1166,7 +1059,7 @@ MESSAGE
1166
1059
 
1167
1060
  def sass_script(*args)
1168
1061
  parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
1169
- :filename => @filename, :importer => @importer)
1062
+ :filename => @filename, :importer => @importer, :allow_extra_text => true)
1170
1063
  result = parser.send(*args)
1171
1064
  unless @strs.empty?
1172
1065
  # Convert to CSS manually so that comments are ignored.
@@ -1191,25 +1084,26 @@ MESSAGE
1191
1084
  :media_expr => "media expression (e.g. (min-device-width: 800px))",
1192
1085
  :at_root_query => "@at-root query (e.g. (without: media))",
1193
1086
  :at_root_directive_list => '* or identifier',
1194
- :pseudo_arg => "expression (e.g. fr, 2n+1)",
1087
+ :pseudo_args => "expression (e.g. fr, 2n+1)",
1195
1088
  :interp_ident => "identifier",
1196
- :interp_name => "identifier",
1197
1089
  :qualified_name => "identifier",
1198
1090
  :expr => "expression (e.g. 1px, bold)",
1199
- :_selector => "selector",
1200
1091
  :selector_comma_sequence => "selector",
1201
- :simple_selector_sequence => "selector",
1092
+ :string => "string",
1202
1093
  :import_arg => "file to import (string or url())",
1203
1094
  :moz_document_function => "matching function (e.g. url-prefix(), domain())",
1204
1095
  :supports_condition => "@supports condition (e.g. (display: flexbox))",
1205
1096
  :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
1097
+ :a_n_plus_b => "An+B expression",
1098
+ :keyframes_selector_component => "from, to, or a percentage",
1099
+ :keyframes_selector => "keyframes selector (e.g. 10%)"
1206
1100
  }
1207
1101
 
1208
1102
  TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
1209
1103
  [Sass::SCSS::RX.const_get(c), c.downcase]
1210
1104
  end).merge(
1211
1105
  IDENT => "identifier",
1212
- /[;}]/ => '";"',
1106
+ /[;{}]/ => '";"',
1213
1107
  /\b(without|with)\b/ => '"with" or "without"'
1214
1108
  )
1215
1109
 
@@ -1230,8 +1124,9 @@ MESSAGE
1230
1124
 
1231
1125
  unless name
1232
1126
  # Display basic regexps as plain old strings
1127
+ source = rx.source.gsub(%r{\\/}, '/')
1233
1128
  string = rx.source.gsub(/\\(.)/, '\1')
1234
- name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
1129
+ name = source == Regexp.escape(string) ? string.inspect : rx.inspect
1235
1130
  end
1236
1131
 
1237
1132
  expected(name)
@@ -1260,14 +1155,20 @@ MESSAGE
1260
1155
  line = @line
1261
1156
  offset = @offset
1262
1157
  expected = @expected
1158
+
1159
+ logger = Sass::Logger::Delayed.install!
1263
1160
  if catch(:_sass_parser_error) {yield; false}
1264
1161
  @scanner.pos = pos
1265
1162
  @line = line
1266
1163
  @offset = offset
1267
1164
  @expected = expected
1268
1165
  {:pos => pos, :line => line, :expected => @expected, :block => block}
1166
+ else
1167
+ logger.flush
1168
+ nil
1269
1169
  end
1270
1170
  ensure
1171
+ logger.uninstall! if logger
1271
1172
  @throw_error = old_throw_error
1272
1173
  end
1273
1174
 
@@ -1314,33 +1215,39 @@ MESSAGE
1314
1215
 
1315
1216
  def tok(rx, last_group_lookahead = false)
1316
1217
  res = @scanner.scan(rx)
1317
- if res
1318
- # This fixes https://github.com/nex3/sass/issues/104, which affects
1319
- # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
1320
- # positive lookahead operator in the Regexp (which matches without
1321
- # consuming the matched group), with a match that does consume the
1322
- # group, but then rewinds the scanner and removes the group from the
1323
- # end of the matched string. This fix makes the assumption that the
1324
- # matched group will always occur at the end of the match.
1325
- if last_group_lookahead && @scanner[-1]
1326
- @scanner.pos -= @scanner[-1].length
1327
- res.slice!(-@scanner[-1].length..-1)
1328
- end
1329
1218
 
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
1219
+ return unless res
1220
+
1221
+ # This fixes https://github.com/nex3/sass/issues/104, which affects
1222
+ # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
1223
+ # positive lookahead operator in the Regexp (which matches without
1224
+ # consuming the matched group), with a match that does consume the
1225
+ # group, but then rewinds the scanner and removes the group from the
1226
+ # end of the matched string. This fix makes the assumption that the
1227
+ # matched group will always occur at the end of the match.
1228
+ if last_group_lookahead && @scanner[-1]
1229
+ @scanner.pos -= @scanner[-1].length
1230
+ res.slice!(-@scanner[-1].length..-1)
1231
+ end
1337
1232
 
1338
- @expected = nil
1339
- if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
1340
- @strs.each {|s| s << res}
1341
- end
1342
- res
1233
+ newline_count = res.count(NEWLINE)
1234
+ if newline_count > 0
1235
+ @line += newline_count
1236
+ @offset = res[res.rindex(NEWLINE)..-1].size
1237
+ else
1238
+ @offset += res.size
1343
1239
  end
1240
+
1241
+ @expected = nil
1242
+ if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
1243
+ @strs.each {|s| s << res}
1244
+ end
1245
+ res
1246
+ end
1247
+
1248
+ # Remove a vendor prefix from `str`.
1249
+ def deprefix(str)
1250
+ str.gsub(/^-[a-zA-Z0-9]+-/, '')
1344
1251
  end
1345
1252
  end
1346
1253
  end