sass 3.3.0.alpha.247 → 3.3.0.alpha.252

Sign up to get free protection for your applications and to get access to all the features.
data/REVISION CHANGED
@@ -1 +1 @@
1
- 1ce6d0118a8ddca0abb5ee27560d3e4982bdfcaa
1
+ 9490465245da8b2913346590c6f0974d1ad039cf
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.3.0.alpha.247
1
+ 3.3.0.alpha.252
@@ -1 +1 @@
1
- 07 September 2013 00:37:00 GMT
1
+ 12 September 2013 19:42:11 GMT
@@ -29,6 +29,7 @@ require 'sass/tree/debug_node'
29
29
  require 'sass/tree/warn_node'
30
30
  require 'sass/tree/import_node'
31
31
  require 'sass/tree/charset_node'
32
+ require 'sass/tree/at_root_node'
32
33
  require 'sass/tree/visitors/base'
33
34
  require 'sass/tree/visitors/perform'
34
35
  require 'sass/tree/visitors/cssize'
@@ -761,109 +762,133 @@ WARNING
761
762
  end
762
763
  end
763
764
 
765
+ DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
766
+ :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
767
+ :at_root]
768
+
764
769
  def parse_directive(parent, line, root)
765
770
  directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
766
771
  offset = directive.size + whitespace.size + 1 if whitespace
767
772
 
768
- # If value begins with url( or ",
769
- # it's a CSS @import rule and we don't want to touch it.
770
- case directive
771
- when 'import'
772
- parse_import(line, value, offset)
773
- when 'mixin'
774
- parse_mixin_definition(line)
775
- when 'content'
776
- parse_content_directive(line)
777
- when 'include'
778
- parse_mixin_include(line, root)
779
- when 'function'
780
- parse_function(line, root)
781
- when 'for'
782
- parse_for(line, root, value)
783
- when 'each'
784
- parse_each(line, root, value)
785
- when 'else'
786
- parse_else(parent, line, value)
787
- when 'while'
788
- raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
789
- Tree::WhileNode.new(parse_script(value, :offset => offset))
790
- when 'if'
791
- raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
792
- Tree::IfNode.new(parse_script(value, :offset => offset))
793
- when 'debug'
794
- raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
795
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
796
- :line => @line + 1) unless line.children.empty?
797
- offset = line.offset + line.text.index(value).to_i
798
- Tree::DebugNode.new(parse_script(value, :offset => offset))
799
- when 'extend'
800
- raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
801
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
802
- :line => @line + 1) unless line.children.empty?
803
- optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '')
804
- offset = line.offset + line.text.index(value).to_i
805
- interp_parsed = parse_interp(value, offset)
806
- selector_range = Sass::Source::Range.new(
807
- Sass::Source::Position.new(@line, to_parser_offset(offset)),
808
- Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
809
- @options[:filename], @options[:importer]
810
- )
811
- Tree::ExtendNode.new(interp_parsed, optional, selector_range)
812
- when 'warn'
813
- raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
814
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
815
- :line => @line + 1) unless line.children.empty?
816
- offset = line.offset + line.text.index(value).to_i
817
- Tree::WarnNode.new(parse_script(value, :offset => offset))
818
- when 'return'
819
- raise SyntaxError.new("Invalid @return: expected expression.") unless value
820
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
821
- :line => @line + 1) unless line.children.empty?
822
- offset = line.offset + line.text.index(value).to_i
823
- Tree::ReturnNode.new(parse_script(value, :offset => offset))
824
- when 'charset'
825
- name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
826
- raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
827
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
828
- :line => @line + 1) unless line.children.empty?
829
- Tree::CharsetNode.new(name)
830
- when 'media'
831
- parser = Sass::SCSS::Parser.new(value,
832
- @options[:filename], @options[:importer],
833
- @line, to_parser_offset(@offset))
834
- # -1 includes the '@' into the range.
835
- offset = line.offset + line.text.index(directive).to_i - 1
836
- parsed_media_query_list = parser.parse_media_query_list.to_a
837
- node = Tree::MediaNode.new(parsed_media_query_list)
838
- node.source_range = Sass::Source::Range.new(
839
- Sass::Source::Position.new(@line, to_parser_offset(offset)),
840
- Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
841
- @options[:filename], @options[:importer])
842
- node
843
- else
844
- unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '')
845
- if unprefixed_directive == 'supports'
846
- parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
847
- return Tree::SupportsNode.new(directive, parser.parse_supports_condition)
848
- end
773
+ directive_name = directive.gsub('-', '_').to_sym
774
+ if DIRECTIVES.include?(directive_name)
775
+ return send("parse_#{directive_name}_directive", parent, line, root, value, offset)
776
+ end
849
777
 
850
- Tree::DirectiveNode.new(
851
- value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
778
+ unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '')
779
+ if unprefixed_directive == 'supports'
780
+ parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
781
+ return Tree::SupportsNode.new(directive, parser.parse_supports_condition)
852
782
  end
783
+
784
+ return Tree::DirectiveNode.new(
785
+ value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
786
+ end
787
+
788
+ def parse_while_directive(parent, line, root, value, offset)
789
+ raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
790
+ Tree::WhileNode.new(parse_script(value, :offset => offset))
791
+ end
792
+
793
+ def parse_if_directive(parent, line, root, value, offset)
794
+ raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
795
+ Tree::IfNode.new(parse_script(value, :offset => offset))
796
+ end
797
+
798
+ def parse_debug_directive(parent, line, root, value, offset)
799
+ raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
800
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
801
+ :line => @line + 1) unless line.children.empty?
802
+ offset = line.offset + line.text.index(value).to_i
803
+ Tree::DebugNode.new(parse_script(value, :offset => offset))
804
+ end
805
+
806
+ def parse_extend_directive(parent, line, root, value, offset)
807
+ raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
808
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
809
+ :line => @line + 1) unless line.children.empty?
810
+ optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '')
811
+ offset = line.offset + line.text.index(value).to_i
812
+ interp_parsed = parse_interp(value, offset)
813
+ selector_range = Sass::Source::Range.new(
814
+ Sass::Source::Position.new(@line, to_parser_offset(offset)),
815
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
816
+ @options[:filename], @options[:importer]
817
+ )
818
+ Tree::ExtendNode.new(interp_parsed, optional, selector_range)
853
819
  end
854
820
 
855
- def parse_for(line, root, text)
856
- var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
821
+ def parse_warn_directive(parent, line, root, value, offset)
822
+ raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
823
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
824
+ :line => @line + 1) unless line.children.empty?
825
+ offset = line.offset + line.text.index(value).to_i
826
+ Tree::WarnNode.new(parse_script(value, :offset => offset))
827
+ end
828
+
829
+ def parse_return_directive(parent, line, root, value, offset)
830
+ raise SyntaxError.new("Invalid @return: expected expression.") unless value
831
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
832
+ :line => @line + 1) unless line.children.empty?
833
+ offset = line.offset + line.text.index(value).to_i
834
+ Tree::ReturnNode.new(parse_script(value, :offset => offset))
835
+ end
836
+
837
+ def parse_charset_directive(parent, line, root, value, offset)
838
+ name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
839
+ raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
840
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
841
+ :line => @line + 1) unless line.children.empty?
842
+ Tree::CharsetNode.new(name)
843
+ end
844
+
845
+ def parse_media_directive(parent, line, root, value, offset)
846
+ parser = Sass::SCSS::Parser.new(value,
847
+ @options[:filename], @options[:importer],
848
+ @line, to_parser_offset(@offset))
849
+ offset = line.offset + line.text.index('media').to_i - 1
850
+ parsed_media_query_list = parser.parse_media_query_list.to_a
851
+ node = Tree::MediaNode.new(parsed_media_query_list)
852
+ node.source_range = Sass::Source::Range.new(
853
+ Sass::Source::Position.new(@line, to_parser_offset(offset)),
854
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
855
+ @options[:filename], @options[:importer])
856
+ node
857
+ end
858
+
859
+ def parse_at_root_directive(parent, line, root, value, offset)
860
+ at_root_node = Sass::Tree::AtRootNode.new
861
+ return at_root_node unless value
862
+
863
+ parsed = parse_interp(value, offset)
864
+ selector_range = Sass::Source::Range.new(
865
+ Sass::Source::Position.new(@line, to_parser_offset(offset)),
866
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
867
+ @options[:filename], @options[:importer])
868
+ rule_node = Tree::RuleNode.new(parsed, full_line_range(line))
869
+
870
+ # The caller expects to automatically add children to the returned node
871
+ # and we want it to add children to the rule node instead, so we
872
+ # manually handle the wiring here and return nil so the caller doesn't
873
+ # duplicate our efforts.
874
+ append_children(rule_node, line.children, false)
875
+ at_root_node << rule_node
876
+ parent << at_root_node
877
+ nil
878
+ end
879
+
880
+ def parse_for_directive(parent, line, root, value, offset)
881
+ var, from_expr, to_name, to_expr = value.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
857
882
 
858
883
  if var.nil? # scan failed, try to figure out why for error message
859
- if text !~ /^[^\s]+/
884
+ if value !~ /^[^\s]+/
860
885
  expected = "variable name"
861
- elsif text !~ /^[^\s]+\s+from\s+.+/
886
+ elsif value !~ /^[^\s]+\s+from\s+.+/
862
887
  expected = "'from <expr>'"
863
888
  else
864
889
  expected = "'to <expr>' or 'through <expr>'"
865
890
  end
866
- raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
891
+ raise SyntaxError.new("Invalid for directive '@for #{value}': expected #{expected}.")
867
892
  end
868
893
  raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
869
894
 
@@ -873,16 +898,16 @@ WARNING
873
898
  Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
874
899
  end
875
900
 
876
- def parse_each(line, root, text)
877
- vars, list_expr = text.scan(/^([^\s]+(?:\s*,\s*[^\s]+)*)\s+in\s+(.+)$/).first
901
+ def parse_each_directive(parent, line, root, value, offset)
902
+ vars, list_expr = value.scan(/^([^\s]+(?:\s*,\s*[^\s]+)*)\s+in\s+(.+)$/).first
878
903
 
879
904
  if vars.nil? # scan failed, try to figure out why for error message
880
- if text !~ /^[^\s]+/
905
+ if value !~ /^[^\s]+/
881
906
  expected = "variable name"
882
- elsif text !~ /^[^\s]+(?:\s*,\s*[^\s]+)*[^\s]+\s+from\s+.+/
907
+ elsif value !~ /^[^\s]+(?:\s*,\s*[^\s]+)*[^\s]+\s+from\s+.+/
883
908
  expected = "'in <expr>'"
884
909
  end
885
- raise SyntaxError.new("Invalid each directive '@each #{text}': expected #{expected}.")
910
+ raise SyntaxError.new("Invalid each directive '@each #{value}': expected #{expected}.")
886
911
  end
887
912
 
888
913
  vars = vars.split(',').map do |var|
@@ -895,13 +920,13 @@ WARNING
895
920
  Tree::EachNode.new(vars, parsed_list)
896
921
  end
897
922
 
898
- def parse_else(parent, line, text)
923
+ def parse_else_directive(parent, line, root, value, offset)
899
924
  previous = parent.children.last
900
925
  raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
901
926
 
902
- if text
903
- if text !~ /^if\s+(.+)/
904
- raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
927
+ if value
928
+ if value !~ /^if\s+(.+)/
929
+ raise SyntaxError.new("Invalid else directive '@else #{value}': expected 'if <expr>'.")
905
930
  end
906
931
  expr = parse_script($1, :offset => line.offset + line.text.index($1))
907
932
  end
@@ -912,7 +937,7 @@ WARNING
912
937
  nil
913
938
  end
914
939
 
915
- def parse_import(line, value, offset)
940
+ def parse_import_directive(parent, line, root, value, offset)
916
941
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
917
942
  :line => @line + 1) unless line.children.empty?
918
943
 
@@ -1001,6 +1026,10 @@ WARNING
1001
1026
  node
1002
1027
  end
1003
1028
 
1029
+ def parse_mixin_directive(parent, line, root, value, offset)
1030
+ parse_mixin_definition(line)
1031
+ end
1032
+
1004
1033
  MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
1005
1034
  def parse_mixin_definition(line)
1006
1035
  name, arg_string = line.text.scan(MIXIN_DEF_RE).first
@@ -1013,7 +1042,7 @@ WARNING
1013
1042
  end
1014
1043
 
1015
1044
  CONTENT_RE = /^@content\s*(.+)?$/
1016
- def parse_content_directive(line)
1045
+ def parse_content_directive(parent, line, root, value, offset)
1017
1046
  trailing = line.text.scan(CONTENT_RE).first.first
1018
1047
  raise SyntaxError.new("Invalid content directive. Trailing characters found: \"#{trailing}\".") unless trailing.nil?
1019
1048
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.",
@@ -1021,6 +1050,10 @@ WARNING
1021
1050
  Tree::ContentNode.new
1022
1051
  end
1023
1052
 
1053
+ def parse_include_directive(parent, line, root, value, offset)
1054
+ parse_mixin_include(line, root)
1055
+ end
1056
+
1024
1057
  MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
1025
1058
  def parse_mixin_include(line, root)
1026
1059
  name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
@@ -1033,7 +1066,7 @@ WARNING
1033
1066
  end
1034
1067
 
1035
1068
  FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
1036
- def parse_function(line, root)
1069
+ def parse_function_directive(parent, line, root, value, offset)
1037
1070
  name, arg_string = line.text.scan(FUNCTION_RE).first
1038
1071
  raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
1039
1072
 
@@ -21,6 +21,7 @@ module Sass
21
21
  attr_reader :options
22
22
  attr_writer :caller
23
23
  attr_writer :content
24
+ attr_writer :selector
24
25
 
25
26
  # @param options [{Symbol => Object}] The options hash. See
26
27
  # {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
@@ -44,6 +45,21 @@ module Sass
44
45
  @content || (@parent && @parent.content)
45
46
  end
46
47
 
48
+ # The selector for the current CSS rule, or nil if there is no
49
+ # current CSS rule.
50
+ #
51
+ # @return [Selector::CommaSequence?] The current selector, with any
52
+ # nesting fully resolved.
53
+ def selector
54
+ parent_selector = caller ? caller.selector : (@parent && @parent.selector)
55
+ return parent_selector unless @selector
56
+ return @selector.resolve_parent_refs(parent_selector) if parent_selector
57
+ return @selector
58
+ end
59
+
60
+ # The top-level Environment object.
61
+ #
62
+ # @return [Environment]
47
63
  def global_env
48
64
  @global_env ||= parent.nil? ? self : parent.global_env
49
65
  end
@@ -95,6 +95,7 @@ module Sass
95
95
  :color => HEXCOLOR,
96
96
  :bool => /(true|false)\b/,
97
97
  :null => /null\b/,
98
+ :selector => /&/,
98
99
  :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")})})},
99
100
  :op => %r{(#{Regexp.union(*OP_NAMES)})},
100
101
  }
@@ -241,8 +242,8 @@ module Sass
241
242
  end
242
243
 
243
244
  variable || string(:double, false) || string(:single, false) || number ||
244
- color || bool || null || string(:uri, false) || raw(UNICODERANGE) ||
245
- special_fun || special_val || ident_op || ident || op
245
+ color || bool || null || selector || string(:uri, false) ||
246
+ raw(UNICODERANGE) || special_fun || special_val || ident_op || ident || op
246
247
  end
247
248
 
248
249
  def variable
@@ -307,6 +308,14 @@ MESSAGE
307
308
  [:null, script_null]
308
309
  end
309
310
 
311
+ def selector
312
+ start_pos = source_position
313
+ return unless scan(REGULAR_EXPRESSIONS[:selector])
314
+ script_selector = Script::Tree::Selector.new
315
+ script_selector.source_range = range(start_pos)
316
+ [:selector, script_selector]
317
+ end
318
+
310
319
  def special_fun
311
320
  return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
312
321
  str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
@@ -494,12 +494,17 @@ RUBY
494
494
  end
495
495
 
496
496
  def number
497
- return literal unless tok = try_tok(:number)
497
+ return selector unless tok = try_tok(:number)
498
498
  num = tok.value
499
499
  num.original = num.to_s unless @in_parens
500
500
  literal_node(num, tok.source_range.start_pos)
501
501
  end
502
502
 
503
+ def selector
504
+ return literal unless tok = try_tok(:selector)
505
+ node(tok.value, tok.source_range.start_pos)
506
+ end
507
+
503
508
  def literal
504
509
  (t = try_tok(:color, :bool, :null)) && (return literal_node(t.value, t.source_range))
505
510
  end
@@ -13,3 +13,4 @@ require 'sass/script/tree/string_interpolation'
13
13
  require 'sass/script/tree/literal'
14
14
  require 'sass/script/tree/list_literal'
15
15
  require 'sass/script/tree/map_literal'
16
+ require 'sass/script/tree/selector'
@@ -102,7 +102,7 @@ module Sass::Script::Tree
102
102
  splat = @splat.perform(environment) if @splat
103
103
  if fn = environment.function(@name)
104
104
  keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
105
- return perform_sass_fn(fn, args, keywords, splat)
105
+ return perform_sass_fn(fn, args, keywords, splat, environment)
106
106
  end
107
107
 
108
108
  ruby_name = @name.tr('-', '_')
@@ -232,8 +232,10 @@ module Sass::Script::Tree
232
232
  args
233
233
  end
234
234
 
235
- def perform_sass_fn(function, args, keywords, splat)
235
+ def perform_sass_fn(function, args, keywords, splat, environment)
236
236
  Sass::Tree::Visitors::Perform.perform_arguments(function, args, keywords, splat) do |env|
237
+ env.caller = Sass::Environment.new(environment)
238
+
237
239
  val = catch :_sass_return do
238
240
  function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)}
239
241
  raise Sass::SyntaxError.new("Function #{@name} finished without @return")
@@ -0,0 +1,29 @@
1
+ module Sass::Script::Tree
2
+ # A SassScript node that will resolve to the current selector.
3
+ class Selector < Node
4
+ def initialize; end
5
+
6
+ def children
7
+ []
8
+ end
9
+
10
+ def to_sass(opts = {})
11
+ '&'
12
+ end
13
+
14
+ def deep_copy
15
+ self.dup
16
+ end
17
+
18
+ protected
19
+
20
+ def _perform(environment)
21
+ return opts(Sass::Script::Value::Null.new) unless selector = environment.selector
22
+ opts(Sass::Script::Value::List.new(selector.members.map do |seq|
23
+ Sass::Script::Value::List.new(seq.members.map do |component|
24
+ Sass::Script::Value::String.new(component.to_s)
25
+ end, :space)
26
+ end, :comma))
27
+ end
28
+ end
29
+ end
@@ -28,10 +28,5 @@ module Sass::Script::Value
28
28
  @keywords_accessed = true
29
29
  @keywords
30
30
  end
31
-
32
- # @see Base#children
33
- def children
34
- super + @keywords.values
35
- end
36
31
  end
37
32
  end
@@ -154,7 +154,7 @@ module Sass
154
154
 
155
155
  DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
156
156
  :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
157
- :_moz_document]
157
+ :_moz_document, :at_root]
158
158
 
159
159
  PREFIXED_DIRECTIVES = Set[:supports]
160
160
 
@@ -471,6 +471,13 @@ module Sass
471
471
  val
472
472
  end
473
473
 
474
+ def at_root_directive(start_pos)
475
+ at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
476
+ return block(at_root_node, :stylesheet) unless rule_node = ruleset
477
+ at_root_node << rule_node
478
+ return at_root_node
479
+ end
480
+
474
481
  # http://www.w3.org/TR/css3-conditional/
475
482
  def supports_directive(name, start_pos)
476
483
  condition = expr!(:supports_condition)
@@ -0,0 +1,15 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing an `@at-root` directive.
4
+ #
5
+ # An `@at-root` directive with a selector is converted to an \{AtRootNode}
6
+ # containing a \{RuleNode} at parse time.
7
+ #
8
+ # @see Sass::Tree
9
+ class AtRootNode < Node
10
+ def bubbles?
11
+ true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -256,6 +256,15 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
256
256
  "#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"
257
257
  end
258
258
 
259
+ def visit_atroot(node)
260
+ if node.children.length == 1 && node.children.first.is_a?(Sass::Tree::RuleNode)
261
+ rule = node.children.first
262
+ "#{tab_str}@at-root #{selector_to_src(rule.rule)}#{visit_children(rule)}"
263
+ else
264
+ "#{tab_str}@at-root#{yield}"
265
+ end
266
+ end
267
+
259
268
  private
260
269
 
261
270
  def interp_to_src(interp)
@@ -214,6 +214,10 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
214
214
  rules
215
215
  end
216
216
 
217
+ def visit_atroot(node)
218
+ yield.children
219
+ end
220
+
217
221
  private
218
222
 
219
223
  def bubble(node)
@@ -322,7 +322,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
322
322
  node.filename, node.options[:importer], node.line)
323
323
  node.parsed_rules ||= parser.parse_selector
324
324
  node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
325
- yield
325
+ with_environment Sass::Environment.new(@environment, node.options) do
326
+ @environment.selector = node.parsed_rules
327
+ node.children = node.children.map {|c| visit(c)}.flatten
328
+ end
329
+ node
326
330
  end
327
331
 
328
332
  # Loads the new variable value into the environment.
@@ -116,7 +116,12 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
116
116
  node.children.each do |child|
117
117
  next if child.invisible?
118
118
  visit(child)
119
- output "\n" unless node.style == :compressed
119
+ unless node.style == :compressed
120
+ output "\n"
121
+ if child.is_a?(Sass::Tree::DirectiveNode) && child.has_children && !child.bubbles?
122
+ output "\n"
123
+ end
124
+ end
120
125
  end
121
126
  rstrip!
122
127
  return "" if @result.empty?
@@ -210,18 +215,19 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
210
215
  end
211
216
  end
212
217
  rstrip!
213
- output(if node.style == :compressed
214
- "}"
215
- else
216
- (node.style == :expanded ? "\n" : " ") + "}\n"
217
- end)
218
+ if node.style == :expanded
219
+ output("\n#{tab_str}")
220
+ elsif node.style != :compressed
221
+ output(" ")
222
+ end
223
+ output("}")
218
224
  ensure
219
225
  @in_directive = was_in_directive
220
226
  end
221
227
 
222
228
  def visit_media(node)
223
229
  with_tabs(@tabs + node.tabs) {visit_directive(node)}
224
- erase! 1 unless node.style == :compressed || node.group_end || @result[-1] != ?\n
230
+ output("\n") if node.group_end
225
231
  end
226
232
 
227
233
  def visit_supports(node)
@@ -1648,6 +1648,42 @@ SASS
1648
1648
  SCSS
1649
1649
  end
1650
1650
 
1651
+ def test_at_root
1652
+ assert_scss_to_sass <<SASS, <<SCSS
1653
+ .foo
1654
+ @at-root
1655
+ .bar
1656
+ a: b
1657
+ .baz
1658
+ c: d
1659
+ SASS
1660
+ .foo {
1661
+ @at-root {
1662
+ .bar {
1663
+ a: b;
1664
+ }
1665
+ .baz {
1666
+ c: d;
1667
+ }
1668
+ }
1669
+ }
1670
+ SCSS
1671
+ end
1672
+
1673
+ def test_at_root_with_selector
1674
+ assert_scss_to_sass <<SASS, <<SCSS
1675
+ .foo
1676
+ @at-root .bar
1677
+ a: b
1678
+ SASS
1679
+ .foo {
1680
+ @at-root .bar {
1681
+ a: b;
1682
+ }
1683
+ }
1684
+ SCSS
1685
+ end
1686
+
1651
1687
  ## Regression Tests
1652
1688
 
1653
1689
  def test_list_in_args
@@ -2453,6 +2453,29 @@ $val: 20
2453
2453
  SASS
2454
2454
  end
2455
2455
 
2456
+ def test_at_root
2457
+ assert_equal <<CSS, render(<<SASS)
2458
+ .bar {
2459
+ a: b; }
2460
+ CSS
2461
+ .foo
2462
+ @at-root
2463
+ .bar
2464
+ a: b
2465
+ SASS
2466
+ end
2467
+
2468
+ def test_at_root_with_selector
2469
+ assert_equal <<CSS, render(<<SASS)
2470
+ .bar {
2471
+ a: b; }
2472
+ CSS
2473
+ .foo
2474
+ @at-root .bar
2475
+ a: b
2476
+ SASS
2477
+ end
2478
+
2456
2479
  # Regression tests
2457
2480
 
2458
2481
  def test_list_separator_with_arg_list
@@ -99,6 +99,10 @@ class SassScriptConversionTest < Test::Unit::TestCase
99
99
  assert_renders "(foo: (bar, baz), bip: bop)"
100
100
  end
101
101
 
102
+ def test_selector
103
+ assert_renders "&"
104
+ end
105
+
102
106
  def self.test_precedence(outer, inner)
103
107
  op_outer = Sass::Script::Lexer::OPERATORS_REVERSE[outer]
104
108
  op_inner = Sass::Script::Lexer::OPERATORS_REVERSE[inner]
@@ -552,6 +552,27 @@ SASS
552
552
  assert_equal "true", resolve("$ie or $undef", {}, env('ie' => Sass::Script::Value::Bool.new(true)))
553
553
  end
554
554
 
555
+ def test_selector
556
+ env = Sass::Environment.new
557
+ assert_equal "true", resolve("& == null", {}, env)
558
+
559
+ env.selector = selector('.foo.bar .baz.bang, .bip.bop')
560
+ assert_equal ".foo.bar .baz.bang, .bip.bop", resolve("&", {}, env)
561
+ assert_equal ".foo.bar .baz.bang", resolve("nth(&, 1)", {}, env)
562
+ assert_equal ".bip.bop", resolve("nth(&, 2)", {}, env)
563
+ assert_equal ".foo.bar", resolve("nth(nth(&, 1), 1)", {}, env)
564
+ assert_equal ".baz.bang", resolve("nth(nth(&, 1), 2)", {}, env)
565
+ assert_equal ".bip.bop", resolve("nth(nth(&, 2), 1)", {}, env)
566
+ assert_equal "string", resolve("type-of(nth(nth(&, 1), 1))", {}, env)
567
+
568
+ env.selector = selector('.foo > .bar')
569
+ assert_equal ".foo > .bar", resolve("&", {}, env)
570
+ assert_equal ".foo > .bar", resolve("nth(&, 1)", {}, env)
571
+ assert_equal ".foo", resolve("nth(nth(&, 1), 1)", {}, env)
572
+ assert_equal ">", resolve("nth(nth(&, 1), 2)", {}, env)
573
+ assert_equal ".bar", resolve("nth(nth(&, 1), 3)", {}, env)
574
+ end
575
+
555
576
  # Regression Tests
556
577
 
557
578
  def test_funcall_has_higher_precedence_than_color_name
@@ -645,6 +666,12 @@ SASS
645
666
  env
646
667
  end
647
668
 
669
+ def selector(str)
670
+ parser = Sass::SCSS::StaticParser.new(
671
+ str, filename_for_test, Sass::Importers::Filesystem.new('.'))
672
+ parser.parse_selector
673
+ end
674
+
648
675
  def test_number_printing
649
676
  assert_equal "1", eval("1")
650
677
  assert_equal "1", eval("1.0")
@@ -1526,6 +1526,251 @@ baz {b: foo()}
1526
1526
  SCSS
1527
1527
  end
1528
1528
 
1529
+ ## @at-root
1530
+
1531
+ def test_simple_at_root
1532
+ assert_equal <<CSS, render(<<SCSS)
1533
+ .bar {
1534
+ a: b; }
1535
+ CSS
1536
+ .foo {
1537
+ @at-root {
1538
+ .bar {a: b}
1539
+ }
1540
+ }
1541
+ SCSS
1542
+ end
1543
+
1544
+ def test_at_root_with_selector
1545
+ assert_equal <<CSS, render(<<SCSS)
1546
+ .bar {
1547
+ a: b; }
1548
+ CSS
1549
+ .foo {
1550
+ @at-root .bar {a: b}
1551
+ }
1552
+ SCSS
1553
+ end
1554
+
1555
+ def test_at_root_in_mixin
1556
+ assert_equal <<CSS, render(<<SCSS)
1557
+ .bar {
1558
+ a: b; }
1559
+ CSS
1560
+ @mixin bar {
1561
+ @at-root .bar {a: b}
1562
+ }
1563
+
1564
+ .foo {
1565
+ @include bar;
1566
+ }
1567
+ SCSS
1568
+ end
1569
+
1570
+ def test_at_root_in_media
1571
+ assert_equal <<CSS, render(<<SCSS)
1572
+ @media screen {
1573
+ .bar {
1574
+ a: b; } }
1575
+ CSS
1576
+ @media screen {
1577
+ .foo {
1578
+ @at-root .bar {a: b}
1579
+ }
1580
+ }
1581
+ SCSS
1582
+ end
1583
+
1584
+ def test_at_root_in_bubbled_media
1585
+ assert_equal <<CSS, render(<<SCSS)
1586
+ @media screen {
1587
+ .bar {
1588
+ a: b; } }
1589
+ CSS
1590
+ .foo {
1591
+ @media screen {
1592
+ @at-root .bar {a: b}
1593
+ }
1594
+ }
1595
+ SCSS
1596
+ end
1597
+
1598
+ def test_at_root_in_unknown_directive
1599
+ assert_equal <<CSS, render(<<SCSS)
1600
+ @fblthp {
1601
+ .bar {
1602
+ a: b; } }
1603
+ CSS
1604
+ @fblthp {
1605
+ .foo {
1606
+ @at-root .bar {a: b}
1607
+ }
1608
+ }
1609
+ SCSS
1610
+ end
1611
+
1612
+ def test_at_root_in_nested_unknown_directive
1613
+ assert_equal <<CSS, render(<<SCSS)
1614
+ .foo {
1615
+ @fblthp {
1616
+ .bar {
1617
+ a: b; } } }
1618
+ CSS
1619
+ .foo {
1620
+ @fblthp {
1621
+ @at-root .bar {a: b}
1622
+ }
1623
+ }
1624
+ SCSS
1625
+ end
1626
+
1627
+ ## Selector Script
1628
+
1629
+ def test_selector_script
1630
+ assert_equal(<<CSS, render(<<SCSS))
1631
+ .foo .bar {
1632
+ content: ".foo .bar"; }
1633
+ CSS
1634
+ .foo .bar {
1635
+ content: "\#{&}";
1636
+ }
1637
+ SCSS
1638
+ end
1639
+
1640
+ def test_nested_selector_script
1641
+ assert_equal(<<CSS, render(<<SCSS))
1642
+ .foo .bar {
1643
+ content: ".foo .bar"; }
1644
+ CSS
1645
+ .foo {
1646
+ .bar {
1647
+ content: "\#{&}";
1648
+ }
1649
+ }
1650
+ SCSS
1651
+ end
1652
+
1653
+ def test_nested_selector_script_with_outer_comma_selector
1654
+ assert_equal(<<CSS, render(<<SCSS))
1655
+ .foo .baz, .bar .baz {
1656
+ content: ".foo .baz, .bar .baz"; }
1657
+ CSS
1658
+ .foo, .bar {
1659
+ .baz {
1660
+ content: "\#{&}";
1661
+ }
1662
+ }
1663
+ SCSS
1664
+ end
1665
+
1666
+ def test_nested_selector_script_with_inner_comma_selector
1667
+ assert_equal(<<CSS, render(<<SCSS))
1668
+ .foo .bar, .foo .baz {
1669
+ content: ".foo .bar, .foo .baz"; }
1670
+ CSS
1671
+ .foo {
1672
+ .bar, .baz {
1673
+ content: "\#{&}";
1674
+ }
1675
+ }
1676
+ SCSS
1677
+ end
1678
+
1679
+ def test_selector_script_through_mixin
1680
+ assert_equal(<<CSS, render(<<SCSS))
1681
+ .foo {
1682
+ content: ".foo"; }
1683
+ CSS
1684
+ @mixin mixin {
1685
+ content: "\#{&}";
1686
+ }
1687
+
1688
+ .foo {
1689
+ @include mixin;
1690
+ }
1691
+ SCSS
1692
+ end
1693
+
1694
+ def test_selector_script_through_content
1695
+ assert_equal(<<CSS, render(<<SCSS))
1696
+ .foo {
1697
+ content: ".foo"; }
1698
+ CSS
1699
+ @mixin mixin {
1700
+ @content;
1701
+ }
1702
+
1703
+ .foo {
1704
+ @include mixin {
1705
+ content: "\#{&}";
1706
+ }
1707
+ }
1708
+ SCSS
1709
+ end
1710
+
1711
+ def test_selector_script_through_function
1712
+ assert_equal(<<CSS, render(<<SCSS))
1713
+ .foo {
1714
+ content: ".foo"; }
1715
+ CSS
1716
+ @function fn() {
1717
+ @return "\#{&}";
1718
+ }
1719
+
1720
+ .foo {
1721
+ content: fn();
1722
+ }
1723
+ SCSS
1724
+ end
1725
+
1726
+ def test_selector_script_through_media
1727
+ assert_equal(<<CSS, render(<<SCSS))
1728
+ .foo {
1729
+ content: "outer"; }
1730
+ @media screen {
1731
+ .foo .bar {
1732
+ content: ".foo .bar"; } }
1733
+ CSS
1734
+ .foo {
1735
+ content: "outer";
1736
+ @media screen {
1737
+ .bar {
1738
+ content: "\#{&}";
1739
+ }
1740
+ }
1741
+ }
1742
+ SCSS
1743
+ end
1744
+
1745
+ def test_selector_script_save_and_reuse
1746
+ assert_equal(<<CSS, render(<<SCSS))
1747
+ .bar {
1748
+ content: ".foo"; }
1749
+ CSS
1750
+ $var: null;
1751
+ .foo {
1752
+ $var: &;
1753
+ }
1754
+
1755
+ .bar {
1756
+ content: "\#{$var}";
1757
+ }
1758
+ SCSS
1759
+ end
1760
+
1761
+ def test_selector_script_with_at_root
1762
+ assert_equal(<<CSS, render(<<SCSS))
1763
+ .foo-bar {
1764
+ a: b; }
1765
+ CSS
1766
+ .foo {
1767
+ @at-root \#{&}-bar {
1768
+ a: b;
1769
+ }
1770
+ }
1771
+ SCSS
1772
+ end
1773
+
1529
1774
  ## Errors
1530
1775
 
1531
1776
  def test_nested_mixin_def_is_scoped
@@ -1736,6 +1981,57 @@ SCSS
1736
1981
 
1737
1982
  # Regression
1738
1983
 
1984
+ def test_nested_unknown_directive
1985
+ assert_equal(<<CSS, render(<<SCSS, :style => :nested))
1986
+ .foo {
1987
+ @fblthp {
1988
+ .bar {
1989
+ a: b; } } }
1990
+ CSS
1991
+ .foo {
1992
+ @fblthp {
1993
+ .bar {a: b}
1994
+ }
1995
+ }
1996
+ SCSS
1997
+
1998
+ assert_equal(<<CSS, render(<<SCSS, :style => :compressed))
1999
+ .foo{@fblthp{.bar{a:b}}}
2000
+ CSS
2001
+ .foo {
2002
+ @fblthp {
2003
+ .bar {a: b}
2004
+ }
2005
+ }
2006
+ SCSS
2007
+
2008
+ assert_equal(<<CSS, render(<<SCSS, :style => :compact))
2009
+ .foo { @fblthp { .bar { a: b; } } }
2010
+ CSS
2011
+ .foo {
2012
+ @fblthp {
2013
+ .bar {a: b}
2014
+ }
2015
+ }
2016
+ SCSS
2017
+
2018
+ assert_equal(<<CSS, render(<<SCSS, :style => :expanded))
2019
+ .foo {
2020
+ @fblthp {
2021
+ .bar {
2022
+ a: b;
2023
+ }
2024
+ }
2025
+ }
2026
+ CSS
2027
+ .foo {
2028
+ @fblthp {
2029
+ .bar {a: b}
2030
+ }
2031
+ }
2032
+ SCSS
2033
+ end
2034
+
1739
2035
  def test_loud_comment_in_compressed_mode
1740
2036
  assert_equal(<<CSS, render(<<SCSS))
1741
2037
  /*! foo */
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass
3
3
  version: !ruby/object:Gem::Version
4
- hash: 592302819
4
+ hash: 592302837
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 3
8
8
  - 3
9
9
  - 0
10
10
  - alpha
11
- - 247
12
- version: 3.3.0.alpha.247
11
+ - 252
12
+ version: 3.3.0.alpha.252
13
13
  platform: ruby
14
14
  authors:
15
15
  - Nathan Weizenbaum
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2013-09-06 00:00:00 -04:00
22
+ date: 2013-09-12 00:00:00 -04:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
@@ -129,6 +129,7 @@ files:
129
129
  - lib/sass/script/tree/map_literal.rb
130
130
  - lib/sass/script/tree/node.rb
131
131
  - lib/sass/script/tree/operation.rb
132
+ - lib/sass/script/tree/selector.rb
132
133
  - lib/sass/script/tree/string_interpolation.rb
133
134
  - lib/sass/script/tree/unary_operation.rb
134
135
  - lib/sass/script/tree/variable.rb
@@ -158,18 +159,18 @@ files:
158
159
  - lib/sass/shared.rb
159
160
  - lib/sass/supports.rb
160
161
  - lib/sass/version.rb
161
- - lib/sass/tree/charset_node.rb
162
162
  - lib/sass/tree/comment_node.rb
163
+ - lib/sass/tree/charset_node.rb
164
+ - lib/sass/tree/debug_node.rb
163
165
  - lib/sass/tree/content_node.rb
164
166
  - lib/sass/tree/css_import_node.rb
165
- - lib/sass/tree/debug_node.rb
166
167
  - lib/sass/tree/directive_node.rb
167
168
  - lib/sass/tree/each_node.rb
168
169
  - lib/sass/tree/extend_node.rb
169
170
  - lib/sass/tree/for_node.rb
170
171
  - lib/sass/tree/function_node.rb
171
- - lib/sass/tree/if_node.rb
172
172
  - lib/sass/tree/import_node.rb
173
+ - lib/sass/tree/if_node.rb
173
174
  - lib/sass/tree/media_node.rb
174
175
  - lib/sass/tree/mixin_def_node.rb
175
176
  - lib/sass/tree/mixin_node.rb
@@ -178,9 +179,10 @@ files:
178
179
  - lib/sass/tree/return_node.rb
179
180
  - lib/sass/tree/root_node.rb
180
181
  - lib/sass/tree/rule_node.rb
181
- - lib/sass/tree/supports_node.rb
182
182
  - lib/sass/tree/trace_node.rb
183
+ - lib/sass/tree/supports_node.rb
183
184
  - lib/sass/tree/variable_node.rb
185
+ - lib/sass/tree/warn_node.rb
184
186
  - lib/sass/tree/visitors/base.rb
185
187
  - lib/sass/tree/visitors/check_nesting.rb
186
188
  - lib/sass/tree/visitors/convert.rb
@@ -190,8 +192,8 @@ files:
190
192
  - lib/sass/tree/visitors/perform.rb
191
193
  - lib/sass/tree/visitors/set_options.rb
192
194
  - lib/sass/tree/visitors/to_css.rb
193
- - lib/sass/tree/warn_node.rb
194
195
  - lib/sass/tree/while_node.rb
196
+ - lib/sass/tree/at_root_node.rb
195
197
  - lib/sass/source/map.rb
196
198
  - lib/sass/source/position.rb
197
199
  - lib/sass/source/range.rb