sass 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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