sass 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +58 -50
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/bin/sass +1 -1
  9. data/bin/scss +1 -1
  10. data/lib/sass/cache_stores/filesystem.rb +6 -2
  11. data/lib/sass/css.rb +1 -3
  12. data/lib/sass/engine.rb +37 -46
  13. data/lib/sass/environment.rb +13 -17
  14. data/lib/sass/error.rb +6 -9
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +424 -0
  18. data/lib/sass/exec.rb +5 -771
  19. data/lib/sass/features.rb +7 -0
  20. data/lib/sass/importers/base.rb +7 -2
  21. data/lib/sass/importers/filesystem.rb +9 -25
  22. data/lib/sass/importers.rb +0 -1
  23. data/lib/sass/media.rb +1 -4
  24. data/lib/sass/plugin/compiler.rb +200 -83
  25. data/lib/sass/plugin/staleness_checker.rb +1 -1
  26. data/lib/sass/plugin.rb +3 -3
  27. data/lib/sass/script/css_lexer.rb +1 -1
  28. data/lib/sass/script/functions.rb +622 -268
  29. data/lib/sass/script/lexer.rb +99 -34
  30. data/lib/sass/script/parser.rb +24 -23
  31. data/lib/sass/script/tree/funcall.rb +1 -1
  32. data/lib/sass/script/tree/interpolation.rb +20 -2
  33. data/lib/sass/script/tree/selector.rb +26 -0
  34. data/lib/sass/script/tree/string_interpolation.rb +1 -1
  35. data/lib/sass/script/tree.rb +1 -0
  36. data/lib/sass/script/value/base.rb +7 -5
  37. data/lib/sass/script/value/bool.rb +0 -5
  38. data/lib/sass/script/value/color.rb +39 -21
  39. data/lib/sass/script/value/helpers.rb +107 -0
  40. data/lib/sass/script/value/list.rb +0 -15
  41. data/lib/sass/script/value/null.rb +0 -5
  42. data/lib/sass/script/value/number.rb +62 -14
  43. data/lib/sass/script/value/string.rb +59 -11
  44. data/lib/sass/script/value.rb +0 -1
  45. data/lib/sass/scss/css_parser.rb +8 -2
  46. data/lib/sass/scss/parser.rb +190 -328
  47. data/lib/sass/scss/rx.rb +15 -6
  48. data/lib/sass/scss/static_parser.rb +298 -1
  49. data/lib/sass/selector/abstract_sequence.rb +28 -13
  50. data/lib/sass/selector/comma_sequence.rb +92 -13
  51. data/lib/sass/selector/pseudo.rb +256 -0
  52. data/lib/sass/selector/sequence.rb +94 -24
  53. data/lib/sass/selector/simple.rb +14 -25
  54. data/lib/sass/selector/simple_sequence.rb +97 -33
  55. data/lib/sass/selector.rb +57 -194
  56. data/lib/sass/shared.rb +1 -1
  57. data/lib/sass/source/map.rb +26 -12
  58. data/lib/sass/stack.rb +0 -6
  59. data/lib/sass/supports.rb +2 -3
  60. data/lib/sass/tree/at_root_node.rb +1 -0
  61. data/lib/sass/tree/charset_node.rb +1 -1
  62. data/lib/sass/tree/directive_node.rb +8 -2
  63. data/lib/sass/tree/error_node.rb +18 -0
  64. data/lib/sass/tree/extend_node.rb +1 -1
  65. data/lib/sass/tree/function_node.rb +4 -0
  66. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  67. data/lib/sass/tree/prop_node.rb +1 -1
  68. data/lib/sass/tree/rule_node.rb +12 -7
  69. data/lib/sass/tree/visitors/check_nesting.rb +38 -10
  70. data/lib/sass/tree/visitors/convert.rb +16 -18
  71. data/lib/sass/tree/visitors/cssize.rb +29 -29
  72. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  73. data/lib/sass/tree/visitors/perform.rb +45 -33
  74. data/lib/sass/tree/visitors/set_options.rb +14 -0
  75. data/lib/sass/tree/visitors/to_css.rb +15 -14
  76. data/lib/sass/util/subset_map.rb +1 -1
  77. data/lib/sass/util.rb +222 -99
  78. data/lib/sass/version.rb +5 -5
  79. data/lib/sass.rb +0 -5
  80. data/test/sass/cache_test.rb +62 -20
  81. data/test/sass/callbacks_test.rb +1 -1
  82. data/test/sass/compiler_test.rb +19 -10
  83. data/test/sass/conversion_test.rb +58 -1
  84. data/test/sass/css2sass_test.rb +23 -4
  85. data/test/sass/encoding_test.rb +219 -0
  86. data/test/sass/engine_test.rb +136 -199
  87. data/test/sass/exec_test.rb +2 -2
  88. data/test/sass/extend_test.rb +236 -19
  89. data/test/sass/functions_test.rb +295 -253
  90. data/test/sass/importer_test.rb +31 -21
  91. data/test/sass/logger_test.rb +1 -1
  92. data/test/sass/more_results/more_import.css +1 -1
  93. data/test/sass/plugin_test.rb +14 -13
  94. data/test/sass/results/compact.css +1 -1
  95. data/test/sass/results/complex.css +4 -4
  96. data/test/sass/results/expanded.css +1 -1
  97. data/test/sass/results/import.css +1 -1
  98. data/test/sass/results/import_charset_ibm866.css +2 -2
  99. data/test/sass/results/mixins.css +17 -17
  100. data/test/sass/results/nested.css +1 -1
  101. data/test/sass/results/parent_ref.css +2 -2
  102. data/test/sass/results/script.css +3 -3
  103. data/test/sass/results/scss_import.css +1 -1
  104. data/test/sass/script_conversion_test.rb +10 -7
  105. data/test/sass/script_test.rb +288 -74
  106. data/test/sass/scss/css_test.rb +141 -24
  107. data/test/sass/scss/rx_test.rb +4 -4
  108. data/test/sass/scss/scss_test.rb +457 -18
  109. data/test/sass/source_map_test.rb +115 -25
  110. data/test/sass/superselector_test.rb +191 -0
  111. data/test/sass/templates/scss_import.scss +2 -1
  112. data/test/sass/test_helper.rb +1 -1
  113. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  114. data/test/sass/util/normalized_map_test.rb +1 -1
  115. data/test/sass/util/subset_map_test.rb +2 -2
  116. data/test/sass/util_test.rb +31 -1
  117. data/test/sass/value_helpers_test.rb +5 -7
  118. data/test/test_helper.rb +2 -2
  119. data/vendor/listen/CHANGELOG.md +1 -228
  120. data/vendor/listen/Gemfile +5 -15
  121. data/vendor/listen/README.md +111 -77
  122. data/vendor/listen/Rakefile +0 -42
  123. data/vendor/listen/lib/listen/adapter.rb +195 -82
  124. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  125. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  126. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  127. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  128. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  129. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  130. data/vendor/listen/lib/listen/listener.rb +135 -37
  131. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  132. data/vendor/listen/lib/listen/version.rb +1 -1
  133. data/vendor/listen/lib/listen.rb +33 -19
  134. data/vendor/listen/listen.gemspec +6 -0
  135. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  136. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  137. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  138. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  139. data/vendor/listen/spec/listen_spec.rb +15 -21
  140. data/vendor/listen/spec/spec_helper.rb +4 -0
  141. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  142. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  143. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  144. metadata +25 -22
  145. data/ext/mkrf_conf.rb +0 -27
  146. data/lib/sass/importers/deprecated_path.rb +0 -51
  147. data/lib/sass/script/value/deprecated_false.rb +0 -55
  148. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  149. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  150. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  151. 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
@@ -39,7 +40,7 @@ module Sass
39
40
  def parse
40
41
  init_scanner!
41
42
  root = stylesheet
42
- expected("selector or at-rule") unless @scanner.eos?
43
+ expected("selector or at-rule") unless root && @scanner.eos?
43
44
  root
44
45
  end
45
46
 
@@ -62,7 +63,7 @@ module Sass
62
63
  def parse_media_query_list
63
64
  init_scanner!
64
65
  ql = media_query_list
65
- expected("media query list") unless @scanner.eos?
66
+ expected("media query list") unless ql && @scanner.eos?
66
67
  ql
67
68
  end
68
69
 
@@ -74,7 +75,7 @@ module Sass
74
75
  def parse_at_root_query
75
76
  init_scanner!
76
77
  query = at_root_query
77
- expected("@at-root query list") unless @scanner.eos?
78
+ expected("@at-root query list") unless query && @scanner.eos?
78
79
  query
79
80
  end
80
81
 
@@ -86,7 +87,7 @@ module Sass
86
87
  def parse_supports_condition
87
88
  init_scanner!
88
89
  condition = supports_condition
89
- expected("supports condition") unless @scanner.eos?
90
+ expected("supports condition") unless condition && @scanner.eos?
90
91
  condition
91
92
  end
92
93
 
@@ -155,10 +156,15 @@ module Sass
155
156
  else
156
157
  value = Sass::Engine.parse_interp(
157
158
  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]/, ' '))
159
+ string_before_comment = @scanner.string[0...@scanner.pos - text.length]
160
+ newline_before_comment = string_before_comment.rindex("\n")
161
+ last_line_before_comment =
162
+ if newline_before_comment
163
+ string_before_comment[newline_before_comment + 1..-1]
164
+ else
165
+ string_before_comment
166
+ end
167
+ value.unshift(last_line_before_comment.gsub(/[^\s]/, ' '))
162
168
  end
163
169
 
164
170
  type = if silent
@@ -175,7 +181,7 @@ module Sass
175
181
 
176
182
  DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
177
183
  :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
178
- :_moz_document, :at_root]
184
+ :_moz_document, :at_root, :error]
179
185
 
180
186
  PREFIXED_DIRECTIVES = Set[:supports]
181
187
 
@@ -191,10 +197,7 @@ module Sass
191
197
  return dir
192
198
  end
193
199
 
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
200
+ val = almost_any_value
198
201
  val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
199
202
  directive_body(val, start_pos)
200
203
  end
@@ -217,7 +220,7 @@ module Sass
217
220
  end
218
221
 
219
222
  def prefixed_directive(name, start_pos)
220
- sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
223
+ sym = deprefix(name).gsub('-', '_').to_sym
221
224
  PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
222
225
  end
223
226
 
@@ -350,10 +353,12 @@ module Sass
350
353
  end
351
354
 
352
355
  def extend_directive(start_pos)
353
- selector, selector_range = expr!(:selector_sequence)
356
+ selector_start_pos = source_position
357
+ @expected = "selector"
358
+ selector = Sass::Util.strip_string_array(expr!(:almost_any_value))
354
359
  optional = tok(OPTIONAL)
355
360
  ss
356
- node(Sass::Tree::ExtendNode.new(selector, !!optional, selector_range), start_pos)
361
+ node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos)
357
362
  end
358
363
 
359
364
  def import_directive(start_pos)
@@ -371,7 +376,7 @@ module Sass
371
376
 
372
377
  def import_arg
373
378
  start_pos = source_position
374
- return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
379
+ return unless (str = string) || (uri = tok?(/url\(/i))
375
380
  if uri
376
381
  str = sass_script(:parse_string)
377
382
  ss
@@ -379,16 +384,15 @@ module Sass
379
384
  ss
380
385
  return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
381
386
  end
382
-
383
- path = @scanner[1] || @scanner[2]
384
387
  ss
385
388
 
386
389
  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)
390
+ if str =~ %r{^(https?:)?//} || media || use_css_import?
391
+ return node(Sass::Tree::CssImportNode.new(
392
+ Sass::Script::Value::String.quote(str), media.to_a), start_pos)
389
393
  end
390
394
 
391
- node(Sass::Tree::ImportNode.new(path.strip), start_pos)
395
+ node(Sass::Tree::ImportNode.new(str.strip), start_pos)
392
396
  end
393
397
 
394
398
  def use_css_import?; false; end
@@ -471,8 +475,7 @@ module Sass
471
475
  alias_method :at_root_query, :query_expr
472
476
 
473
477
  def charset_directive(start_pos)
474
- tok! STRING
475
- name = @scanner[1] || @scanner[2]
478
+ name = expr!(:string)
476
479
  ss
477
480
  node(Sass::Tree::CharsetNode.new(name), start_pos)
478
481
  end
@@ -529,6 +532,10 @@ module Sass
529
532
  arr
530
533
  end
531
534
 
535
+ def error_directive(start_pos)
536
+ node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos)
537
+ end
538
+
532
539
  # http://www.w3.org/TR/css3-conditional/
533
540
  def supports_directive(name, start_pos)
534
541
  condition = expr!(:supports_condition)
@@ -624,10 +631,9 @@ module Sass
624
631
 
625
632
  def ruleset
626
633
  start_pos = source_position
627
- rules, source_range = selector_sequence
628
- return unless rules
634
+ return unless (rules = almost_any_value)
629
635
  block(node(
630
- Sass::Tree::RuleNode.new(rules.flatten.compact, source_range), start_pos), :ruleset)
636
+ Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset)
631
637
  end
632
638
 
633
639
  def block(node, context)
@@ -661,315 +667,171 @@ module Sass
661
667
  child_or_array.has_children
662
668
  end
663
669
 
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.
670
+ # When parsing the contents of a ruleset, it can be difficult to tell
671
+ # declarations apart from nested rulesets. Since we don't thoroughly parse
672
+ # selectors until after resolving interpolation, we can share a bunch of
673
+ # the parsing of the two, but we need to disambiguate them first. We use
674
+ # the following criteria:
675
+ #
676
+ # * If the entity doesn't start with an identifier followed by a colon,
677
+ # it's a selector. There are some additional mostly-unimportant cases
678
+ # here to support various declaration hacks.
679
+ #
680
+ # * If the colon is followed by another colon, it's a selector.
670
681
  #
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.
682
+ # * Otherwise, if the colon is followed by anything other than
683
+ # interpolation or a character that's valid as the beginning of an
684
+ # identifier, it's a declaration.
674
685
  #
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.
686
+ # * If the colon is followed by interpolation or a valid identifier, try
687
+ # parsing it as a declaration value. If this fails, backtrack and parse
688
+ # it as a selector.
689
+ #
690
+ # * If the declaration value value valid but is followed by "{", backtrack
691
+ # and parse it as a selector anyway. This ensures that ".foo:bar {" is
692
+ # always parsed as a selector and never as a property with nested
693
+ # properties beneath it.
679
694
  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
695
  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
696
+ declaration = try_declaration
720
697
 
721
- def selector
722
- sel = _selector
723
- return unless sel
724
- sel.to_a
725
- end
726
-
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
698
+ if declaration.nil?
699
+ return unless (selector = almost_any_value)
700
+ elsif declaration.is_a?(Array)
701
+ selector = declaration
702
+ else
703
+ # Declaration should be a PropNode.
704
+ return declaration
741
705
  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
706
 
754
- while (val = combinator || simple_selector_sequence)
755
- res << val
756
- res << "\n" if str {ss}.include?("\n")
707
+ if (additional_selector = almost_any_value)
708
+ selector << additional_selector
757
709
  end
758
- Selector::Sequence.new(res.compact)
759
- end
760
-
761
- def combinator
762
- tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
763
- end
764
710
 
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
711
+ block(node(
712
+ Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset)
774
713
  end
775
714
 
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
715
+ # Tries to parse a declaration, and returns the value parsed so far if it
716
+ # fails.
717
+ #
718
+ # This has three possible return types. It can return `nil`, indicating
719
+ # that parsing failed completely and the scanner hasn't moved forward at
720
+ # all. It can return an Array, indicating that parsing failed after
721
+ # consuming some text (possibly containing interpolation), which is
722
+ # returned. Or it can return a PropNode, indicating that parsing
723
+ # succeeded.
724
+ def try_declaration
725
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
726
+ # val" hacks.
727
+ name_start_pos = source_position
728
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
729
+ name = [s, str {ss}]
730
+ return name unless (ident = interp_ident)
731
+ name << ident
732
+ else
733
+ return unless (name = interp_ident)
734
+ name = Array(name)
792
735
  end
793
736
 
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
737
+ if (comment = tok(COMMENT))
738
+ name << comment
808
739
  end
740
+ name_end_pos = source_position
809
741
 
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
742
+ mid = [str {ss}]
743
+ return name + mid unless tok(/:/)
744
+ mid << ':'
745
+ return name + mid + [':'] if tok(/:/)
746
+ mid << str {ss}
747
+ post_colon_whitespace = !mid.last.empty?
748
+ could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START))
839
749
 
840
- if name == '*'
841
- Selector::Universal.new(merge(ns))
842
- else
843
- Selector::Element.new(merge(name), merge(ns))
750
+ value_start_pos = source_position
751
+ value = nil
752
+ error = catch_error do
753
+ value = value!
754
+ if tok?(/\{/)
755
+ # Properties that are ambiguous with selectors can't have additional
756
+ # properties nested beneath them.
757
+ tok!(/;/) if could_be_selector
758
+ elsif !tok?(/[;{}]/)
759
+ # We want an exception if there's no valid end-of-property character
760
+ # exists, but we don't want to consume it if it does.
761
+ tok!(/[;{}]/)
762
+ end
844
763
  end
845
- end
846
764
 
847
- def qualified_name(allow_star_name = false)
848
- name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
849
- return unless name
850
- return nil, name unless tok(/\|/)
765
+ if error
766
+ rethrow error unless could_be_selector
851
767
 
852
- return name, expr!(:interp_ident) unless allow_star_name
853
- @expected = "identifier or *"
854
- return name, interp_ident || tok!(/\*/)
855
- end
768
+ # If the value would be followed by a semicolon, it's definitely
769
+ # supposed to be a property, not a selector.
770
+ additional_selector = almost_any_value
771
+ rethrow error if tok?(/;/)
856
772
 
857
- def interpolation_selector
858
- if (script = interpolation)
859
- Selector::Interpolation.new(script)
773
+ return name + mid + (additional_selector || [])
860
774
  end
861
- end
862
775
 
863
- def attrib
864
- return unless tok(/\[/)
865
- ss
866
- ns, name = attrib_name!
776
+ value_end_pos = source_position
867
777
  ss
778
+ require_block = tok?(/\{/)
868
779
 
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
780
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
781
+ name_start_pos, value_end_pos)
782
+ node.name_source_range = range(name_start_pos, name_end_pos)
783
+ node.value_source_range = range(value_start_pos, value_end_pos)
886
784
 
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
785
+ return node unless require_block
786
+ nested_properties! node
903
787
  end
904
788
 
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 '")"'
789
+ # This production is similar to the CSS [`<any-value>`][any-value]
790
+ # production, but as the name implies, not quite the same. It's meant to
791
+ # consume values that could be a selector, an expression, or a combination
792
+ # of both. It respects strings and comments and supports interpolation. It
793
+ # will consume up to "{", "}", ";", or "!".
794
+ #
795
+ # [any-value]: http://dev.w3.org/csswg/css-variables/#typedef-any-value
796
+ #
797
+ # Values consumed by this production will usually be parsed more
798
+ # thoroughly once interpolation has been resolved.
799
+ def almost_any_value
800
+ return unless (tok = almost_any_value_token)
801
+ sel = [tok]
802
+ while (tok = almost_any_value_token)
803
+ sel << tok
938
804
  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
805
+ merge(sel)
946
806
  end
947
807
 
948
- def pseudo_expr_token
949
- tok(PLUS) || tok(/[-*]/) || tok(NUMBER) || interp_string || tok(IDENT) || interpolation
950
- end
951
-
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}
958
- end
959
- res
808
+ def almost_any_value_token
809
+ tok(%r{
810
+ (
811
+ (?!url\()
812
+ [^"/\#!;\{\}] # "
813
+ |
814
+ /(?![/*])
815
+ |
816
+ \#(?!\{)
817
+ |
818
+ !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed.
819
+ )+
820
+ }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri ||
821
+ interpolation(:warn_for_color)
960
822
  end
961
823
 
962
824
  def declaration
963
- # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
825
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
826
+ # val" hacks.
964
827
  name_start_pos = source_position
965
828
  if (s = tok(/[:\*\.]|\#(?!\{)/))
966
- @use_property_exception = s !~ /[\.\#]/
967
829
  name = [s, str {ss}, *expr!(:interp_ident)]
968
830
  else
969
- name = interp_ident
970
- return unless name
971
- name = [name] if name.is_a?(String)
831
+ return unless (name = interp_ident)
832
+ name = Array(name)
972
833
  end
834
+
973
835
  if (comment = tok(COMMENT))
974
836
  name << comment
975
837
  end
@@ -977,7 +839,9 @@ module Sass
977
839
  ss
978
840
 
979
841
  tok!(/:/)
980
- value_start_pos, space, value = value!
842
+ ss
843
+ value_start_pos = source_position
844
+ value = value!
981
845
  value_end_pos = source_position
982
846
  ss
983
847
  require_block = tok?(/\{/)
@@ -988,19 +852,15 @@ module Sass
988
852
  node.value_source_range = range(value_start_pos, value_end_pos)
989
853
 
990
854
  return node unless require_block
991
- nested_properties! node, space
855
+ nested_properties! node
992
856
  end
993
857
 
994
858
  def value!
995
- space = !str {ss}.empty?
996
- value_start_pos = source_position
997
- @use_property_exception ||= space || !tok?(IDENT)
998
-
999
859
  if tok?(/\{/)
1000
860
  str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
1001
861
  str.line = source_position.line
1002
862
  str.source_range = range(source_position)
1003
- return value_start_pos, true, str
863
+ return str
1004
864
  end
1005
865
 
1006
866
  start_pos = source_position
@@ -1013,18 +873,12 @@ module Sass
1013
873
  str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
1014
874
  str.line = start_pos.line
1015
875
  str.source_range = range(start_pos)
1016
- return value_start_pos, space, str
876
+ return str
1017
877
  end
1018
- return value_start_pos, space, sass_script(:parse)
878
+ sass_script(:parse)
1019
879
  end
1020
880
 
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
1026
-
1027
- @use_property_exception = true
881
+ def nested_properties!(node)
1028
882
  @expected = 'expression (e.g. 1px, bold) or "{"'
1029
883
  block(node, :property)
1030
884
  end
@@ -1078,9 +932,14 @@ MESSAGE
1078
932
  var
1079
933
  end
1080
934
 
1081
- def interpolation
935
+ def interpolation(warn_for_color = false)
1082
936
  return unless tok(INTERP_START)
1083
- sass_script(:parse_interpolated)
937
+ sass_script(:parse_interpolated, warn_for_color)
938
+ end
939
+
940
+ def string
941
+ return unless tok(STRING)
942
+ Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
1084
943
  end
1085
944
 
1086
945
  def interp_string
@@ -1107,10 +966,10 @@ MESSAGE
1107
966
  end
1108
967
 
1109
968
  def interp_ident(start = IDENT)
1110
- val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
969
+ val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP, true)
1111
970
  return unless val
1112
971
  res = [val]
1113
- while (val = tok(NAME) || interpolation)
972
+ while (val = tok(NAME) || interpolation(:warn_for_color))
1114
973
  res << val
1115
974
  end
1116
975
  res
@@ -1123,10 +982,6 @@ MESSAGE
1123
982
  return [var] if var
1124
983
  end
1125
984
 
1126
- def interp_name
1127
- interp_ident NAME
1128
- end
1129
-
1130
985
  def str
1131
986
  @strs.push ""
1132
987
  yield
@@ -1191,25 +1046,26 @@ MESSAGE
1191
1046
  :media_expr => "media expression (e.g. (min-device-width: 800px))",
1192
1047
  :at_root_query => "@at-root query (e.g. (without: media))",
1193
1048
  :at_root_directive_list => '* or identifier',
1194
- :pseudo_arg => "expression (e.g. fr, 2n+1)",
1049
+ :pseudo_args => "expression (e.g. fr, 2n+1)",
1195
1050
  :interp_ident => "identifier",
1196
- :interp_name => "identifier",
1197
1051
  :qualified_name => "identifier",
1198
1052
  :expr => "expression (e.g. 1px, bold)",
1199
- :_selector => "selector",
1200
1053
  :selector_comma_sequence => "selector",
1201
- :simple_selector_sequence => "selector",
1054
+ :string => "string",
1202
1055
  :import_arg => "file to import (string or url())",
1203
1056
  :moz_document_function => "matching function (e.g. url-prefix(), domain())",
1204
1057
  :supports_condition => "@supports condition (e.g. (display: flexbox))",
1205
1058
  :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
1059
+ :a_n_plus_b => "An+B expression",
1060
+ :keyframes_selector_component => "from, to, or a percentage",
1061
+ :keyframes_selector => "keyframes selector (e.g. 10%)"
1206
1062
  }
1207
1063
 
1208
1064
  TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
1209
1065
  [Sass::SCSS::RX.const_get(c), c.downcase]
1210
1066
  end).merge(
1211
1067
  IDENT => "identifier",
1212
- /[;}]/ => '";"',
1068
+ /[;{}]/ => '";"',
1213
1069
  /\b(without|with)\b/ => '"with" or "without"'
1214
1070
  )
1215
1071
 
@@ -1230,8 +1086,9 @@ MESSAGE
1230
1086
 
1231
1087
  unless name
1232
1088
  # Display basic regexps as plain old strings
1089
+ source = rx.source.gsub(/\\\//, '/')
1233
1090
  string = rx.source.gsub(/\\(.)/, '\1')
1234
- name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
1091
+ name = source == Regexp.escape(string) ? string.inspect : rx.inspect
1235
1092
  end
1236
1093
 
1237
1094
  expected(name)
@@ -1342,6 +1199,11 @@ MESSAGE
1342
1199
  res
1343
1200
  end
1344
1201
  end
1202
+
1203
+ # Remove a vendor prefix from `str`.
1204
+ def deprefix(str)
1205
+ str.gsub(/^-[a-zA-Z0-9]+-/, '')
1206
+ end
1345
1207
  end
1346
1208
  end
1347
1209
  end