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

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.
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