sass 3.4.0 → 3.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/CONTRIBUTING.md +148 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +26 -20
  7. data/Rakefile +103 -20
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/extra/sass-spec-ref.sh +32 -0
  11. data/extra/update_watch.rb +1 -1
  12. data/lib/sass/cache_stores/filesystem.rb +7 -7
  13. data/lib/sass/cache_stores/memory.rb +4 -5
  14. data/lib/sass/callbacks.rb +2 -2
  15. data/lib/sass/css.rb +11 -10
  16. data/lib/sass/deprecation.rb +55 -0
  17. data/lib/sass/engine.rb +83 -38
  18. data/lib/sass/environment.rb +26 -2
  19. data/lib/sass/error.rb +12 -12
  20. data/lib/sass/exec/base.rb +15 -3
  21. data/lib/sass/exec/sass_convert.rb +34 -15
  22. data/lib/sass/exec/sass_scss.rb +23 -7
  23. data/lib/sass/features.rb +2 -2
  24. data/lib/sass/importers/base.rb +1 -1
  25. data/lib/sass/importers/deprecated_path.rb +51 -0
  26. data/lib/sass/importers/filesystem.rb +24 -16
  27. data/lib/sass/importers.rb +1 -0
  28. data/lib/sass/logger/base.rb +8 -2
  29. data/lib/sass/logger/delayed.rb +50 -0
  30. data/lib/sass/logger.rb +8 -3
  31. data/lib/sass/plugin/compiler.rb +42 -25
  32. data/lib/sass/plugin/configuration.rb +38 -22
  33. data/lib/sass/plugin/merb.rb +2 -2
  34. data/lib/sass/plugin/rack.rb +3 -3
  35. data/lib/sass/plugin/rails.rb +1 -1
  36. data/lib/sass/plugin/staleness_checker.rb +3 -3
  37. data/lib/sass/plugin.rb +3 -2
  38. data/lib/sass/script/css_parser.rb +2 -3
  39. data/lib/sass/script/css_variable_warning.rb +52 -0
  40. data/lib/sass/script/functions.rb +140 -73
  41. data/lib/sass/script/lexer.rb +37 -22
  42. data/lib/sass/script/parser.rb +235 -40
  43. data/lib/sass/script/tree/funcall.rb +12 -5
  44. data/lib/sass/script/tree/interpolation.rb +109 -4
  45. data/lib/sass/script/tree/list_literal.rb +31 -4
  46. data/lib/sass/script/tree/literal.rb +4 -0
  47. data/lib/sass/script/tree/node.rb +21 -3
  48. data/lib/sass/script/tree/operation.rb +54 -1
  49. data/lib/sass/script/tree/string_interpolation.rb +58 -37
  50. data/lib/sass/script/tree/variable.rb +1 -1
  51. data/lib/sass/script/value/base.rb +10 -9
  52. data/lib/sass/script/value/color.rb +42 -24
  53. data/lib/sass/script/value/helpers.rb +16 -6
  54. data/lib/sass/script/value/map.rb +1 -1
  55. data/lib/sass/script/value/number.rb +52 -19
  56. data/lib/sass/script/value/string.rb +46 -5
  57. data/lib/sass/script.rb +3 -3
  58. data/lib/sass/scss/css_parser.rb +16 -2
  59. data/lib/sass/scss/parser.rb +120 -75
  60. data/lib/sass/scss/rx.rb +9 -10
  61. data/lib/sass/scss/static_parser.rb +19 -14
  62. data/lib/sass/scss.rb +0 -2
  63. data/lib/sass/selector/abstract_sequence.rb +8 -6
  64. data/lib/sass/selector/comma_sequence.rb +25 -9
  65. data/lib/sass/selector/pseudo.rb +45 -35
  66. data/lib/sass/selector/sequence.rb +54 -18
  67. data/lib/sass/selector/simple.rb +11 -11
  68. data/lib/sass/selector/simple_sequence.rb +34 -15
  69. data/lib/sass/selector.rb +7 -10
  70. data/lib/sass/shared.rb +1 -1
  71. data/lib/sass/source/map.rb +7 -4
  72. data/lib/sass/source/position.rb +4 -4
  73. data/lib/sass/stack.rb +2 -2
  74. data/lib/sass/supports.rb +8 -10
  75. data/lib/sass/tree/comment_node.rb +1 -1
  76. data/lib/sass/tree/css_import_node.rb +9 -1
  77. data/lib/sass/tree/function_node.rb +8 -3
  78. data/lib/sass/tree/import_node.rb +6 -5
  79. data/lib/sass/tree/node.rb +5 -3
  80. data/lib/sass/tree/prop_node.rb +5 -6
  81. data/lib/sass/tree/rule_node.rb +14 -4
  82. data/lib/sass/tree/visitors/check_nesting.rb +18 -22
  83. data/lib/sass/tree/visitors/convert.rb +43 -26
  84. data/lib/sass/tree/visitors/cssize.rb +5 -1
  85. data/lib/sass/tree/visitors/deep_copy.rb +1 -1
  86. data/lib/sass/tree/visitors/extend.rb +15 -13
  87. data/lib/sass/tree/visitors/perform.rb +42 -17
  88. data/lib/sass/tree/visitors/set_options.rb +1 -1
  89. data/lib/sass/tree/visitors/to_css.rb +58 -30
  90. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  91. data/lib/sass/util/normalized_map.rb +0 -1
  92. data/lib/sass/util/subset_map.rb +1 -2
  93. data/lib/sass/util.rb +125 -68
  94. data/lib/sass/version.rb +2 -2
  95. data/lib/sass.rb +10 -3
  96. data/test/sass/compiler_test.rb +6 -2
  97. data/test/sass/conversion_test.rb +187 -53
  98. data/test/sass/css2sass_test.rb +50 -1
  99. data/test/sass/css_variable_test.rb +132 -0
  100. data/test/sass/engine_test.rb +207 -61
  101. data/test/sass/exec_test.rb +10 -0
  102. data/test/sass/extend_test.rb +101 -29
  103. data/test/sass/functions_test.rb +60 -9
  104. data/test/sass/importer_test.rb +9 -0
  105. data/test/sass/more_templates/more1.sass +10 -10
  106. data/test/sass/more_templates/more_import.sass +2 -2
  107. data/test/sass/plugin_test.rb +10 -8
  108. data/test/sass/results/script.css +3 -3
  109. data/test/sass/script_conversion_test.rb +58 -29
  110. data/test/sass/script_test.rb +430 -53
  111. data/test/sass/scss/css_test.rb +73 -7
  112. data/test/sass/scss/rx_test.rb +4 -0
  113. data/test/sass/scss/scss_test.rb +309 -4
  114. data/test/sass/source_map_test.rb +152 -74
  115. data/test/sass/superselector_test.rb +19 -0
  116. data/test/sass/templates/_partial.sass +1 -1
  117. data/test/sass/templates/basic.sass +10 -10
  118. data/test/sass/templates/bork1.sass +1 -1
  119. data/test/sass/templates/bork5.sass +1 -1
  120. data/test/sass/templates/compact.sass +10 -10
  121. data/test/sass/templates/complex.sass +187 -187
  122. data/test/sass/templates/compressed.sass +10 -10
  123. data/test/sass/templates/expanded.sass +10 -10
  124. data/test/sass/templates/import.sass +2 -2
  125. data/test/sass/templates/importee.sass +3 -3
  126. data/test/sass/templates/mixins.sass +22 -22
  127. data/test/sass/templates/multiline.sass +4 -4
  128. data/test/sass/templates/nested.sass +13 -13
  129. data/test/sass/templates/parent_ref.sass +12 -12
  130. data/test/sass/templates/script.sass +70 -70
  131. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
  132. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
  133. data/test/sass/templates/subdir/subdir.sass +3 -3
  134. data/test/sass/templates/units.sass +10 -10
  135. data/test/sass/util/multibyte_string_scanner_test.rb +10 -2
  136. data/test/sass/util_test.rb +15 -44
  137. data/test/sass-spec.yml +3 -0
  138. data/test/test_helper.rb +5 -4
  139. metadata +302 -295
  140. data/CONTRIBUTING +0 -3
  141. data/lib/sass/scss/script_lexer.rb +0 -15
  142. data/lib/sass/scss/script_parser.rb +0 -25
@@ -15,6 +15,11 @@ module Sass::Tree
15
15
  # @return [String]
16
16
  attr_accessor :resolved_uri
17
17
 
18
+ # The supports condition for this import.
19
+ #
20
+ # @return [Sass::Supports::Condition]
21
+ attr_accessor :supports_condition
22
+
18
23
  # The media query for this rule, interspersed with
19
24
  # {Sass::Script::Tree::Node}s representing `#{}`-interpolation. Any adjacent
20
25
  # strings will be merged together.
@@ -30,9 +35,11 @@ module Sass::Tree
30
35
 
31
36
  # @param uri [String, Sass::Script::Tree::Node] See \{#uri}
32
37
  # @param query [Array<String, Sass::Script::Tree::Node>] See \{#query}
33
- def initialize(uri, query = [])
38
+ # @param supports_condition [Sass::Supports::Condition] See \{#supports_condition}
39
+ def initialize(uri, query = [], supports_condition = nil)
34
40
  @uri = uri
35
41
  @query = query
42
+ @supports_condition = supports_condition
36
43
  super('')
37
44
  end
38
45
 
@@ -52,6 +59,7 @@ module Sass::Tree
52
59
  @resolved_value ||=
53
60
  begin
54
61
  str = "@import #{resolved_uri}"
62
+ str << " supports(#{supports_condition.to_css})" if supports_condition
55
63
  str << " #{resolved_query.to_css}" if resolved_query
56
64
  str
57
65
  end
@@ -20,6 +20,12 @@ module Sass
20
20
  # @return [Script::Tree::Node?]
21
21
  attr_accessor :splat
22
22
 
23
+ # Strips out any vendor prefixes.
24
+ # @return [String] The normalized name of the directive.
25
+ def normalized_name
26
+ @normalized_name ||= name.gsub(/^(?:-[a-zA-Z0-9]+-)?/, '\1')
27
+ end
28
+
23
29
  # @param name [String] The function name
24
30
  # @param args [Array<(Script::Tree::Node, Script::Tree::Node)>]
25
31
  # The arguments for the function.
@@ -30,9 +36,8 @@ module Sass
30
36
  @splat = splat
31
37
  super()
32
38
 
33
- if %w[and or not].include?(name)
34
- raise Sass::SyntaxError.new("Invalid function name \"#{name}\".")
35
- end
39
+ return unless %w(and or not).include?(name)
40
+ raise Sass::SyntaxError.new("Invalid function name \"#{name}\".")
36
41
  end
37
42
  end
38
43
  end
@@ -55,13 +55,14 @@ module Sass
55
55
  return f if f
56
56
  end
57
57
 
58
- message = "File to import not found or unreadable: #{@imported_filename}.\n"
58
+ lines = ["File to import not found or unreadable: #{@imported_filename}."]
59
+
59
60
  if paths.size == 1
60
- message << "Load path: #{paths.first}"
61
- else
62
- message << "Load paths:\n " << paths.join("\n ")
61
+ lines << "Load path: #{paths.first}"
62
+ elsif !paths.empty?
63
+ lines << "Load paths:\n #{paths.join("\n ")}"
63
64
  end
64
- raise SyntaxError.new(message)
65
+ raise SyntaxError.new(lines.join("\n"))
65
66
  rescue SyntaxError => e
66
67
  raise SyntaxError.new(e.message, :line => line, :filename => @filename)
67
68
  end
@@ -69,7 +69,7 @@ module Sass
69
69
 
70
70
  # The line of the document on which this node appeared.
71
71
  #
72
- # @return [Fixnum]
72
+ # @return [Integer]
73
73
  attr_accessor :line
74
74
 
75
75
  # The source range in the document on which this node appeared.
@@ -83,13 +83,15 @@ module Sass
83
83
  attr_writer :filename
84
84
 
85
85
  # The options hash for the node.
86
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
86
+ # See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
87
87
  #
88
88
  # @return [{Symbol => Object}]
89
89
  attr_reader :options
90
90
 
91
91
  def initialize
92
92
  @children = []
93
+ @filename = nil
94
+ @options = nil
93
95
  end
94
96
 
95
97
  # Sets the options hash for the node and all its children.
@@ -149,7 +151,7 @@ module Sass
149
151
  # @return [Boolean]
150
152
  def invisible?; false; end
151
153
 
152
- # The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
154
+ # The output style. See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
153
155
  #
154
156
  # @return [Symbol]
155
157
  def style
@@ -39,7 +39,7 @@ module Sass::Tree
39
39
  # * This is a child property of another property
40
40
  # * The parent property has a value, and thus will be rendered
41
41
  #
42
- # @return [Fixnum]
42
+ # @return [Integer]
43
43
  attr_accessor :tabs
44
44
 
45
45
  # The source range in which the property name appears.
@@ -119,11 +119,10 @@ module Sass::Tree
119
119
  private
120
120
 
121
121
  def check!
122
- if @options[:property_syntax] && @options[:property_syntax] != @prop_syntax
123
- raise Sass::SyntaxError.new(
124
- "Illegal property syntax: can't use #{@prop_syntax} syntax when " +
125
- ":property_syntax => #{@options[:property_syntax].inspect} is set.")
126
- end
122
+ return unless @options[:property_syntax] && @options[:property_syntax] != @prop_syntax
123
+ raise Sass::SyntaxError.new(
124
+ "Illegal property syntax: can't use #{@prop_syntax} syntax when " +
125
+ ":property_syntax => #{@options[:property_syntax].inspect} is set.")
127
126
  end
128
127
 
129
128
  class << self
@@ -40,7 +40,7 @@ module Sass::Tree
40
40
  # * This is a child rule of another rule
41
41
  # * The parent rule has properties, and thus will be rendered
42
42
  #
43
- # @return [Fixnum]
43
+ # @return [Integer]
44
44
  attr_accessor :tabs
45
45
 
46
46
  # The entire selector source range for this rule.
@@ -132,14 +132,24 @@ module Sass::Tree
132
132
  private
133
133
 
134
134
  def try_to_parse_non_interpolated_rules
135
- if @rule.all? {|t| t.kind_of?(String)}
136
- # We don't use real filename/line info because we don't have it yet.
137
- # When we get it, we'll set it on the parsed rules if possible.
135
+ @parsed_rules = nil
136
+ return unless @rule.all? {|t| t.is_a?(String)}
137
+
138
+ # We don't use real filename/line info because we don't have it yet.
139
+ # When we get it, we'll set it on the parsed rules if possible.
140
+ parser = nil
141
+ warnings = Sass::Util.silence_warnings do
138
142
  parser = Sass::SCSS::StaticParser.new(@rule.join.strip, nil, nil, 1)
139
143
  # rubocop:disable RescueModifier
140
144
  @parsed_rules = parser.parse_selector rescue nil
141
145
  # rubocop:enable RescueModifier
146
+
147
+ $stderr.string
142
148
  end
149
+
150
+ # If parsing produces a warning, throw away the result so we can parse
151
+ # later with the real filename info.
152
+ @parsed_rules = nil unless warnings.empty?
143
153
  end
144
154
  end
145
155
  end
@@ -4,6 +4,8 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
4
4
 
5
5
  def initialize
6
6
  @parents = []
7
+ @parent = nil
8
+ @current_mixin_def = nil
7
9
  end
8
10
 
9
11
  def visit(node)
@@ -90,9 +92,8 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
90
92
 
91
93
  VALID_EXTEND_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
92
94
  def invalid_extend_parent?(parent, child)
93
- unless is_any_of?(parent, VALID_EXTEND_PARENTS)
94
- return "Extend directives may only be used within rules."
95
- end
95
+ return if is_any_of?(parent, VALID_EXTEND_PARENTS)
96
+ "Extend directives may only be used within rules."
96
97
  end
97
98
 
98
99
  INVALID_IMPORT_PARENTS = CONTROL_NODES +
@@ -110,15 +111,13 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
110
111
  end
111
112
 
112
113
  def invalid_mixindef_parent?(parent, child)
113
- unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
114
- return "Mixins may not be defined within control directives or other mixins."
115
- end
114
+ return if (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
115
+ "Mixins may not be defined within control directives or other mixins."
116
116
  end
117
117
 
118
118
  def invalid_function_parent?(parent, child)
119
- unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
120
- return "Functions may not be defined within control directives or other mixins."
121
- end
119
+ return if (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
120
+ "Functions may not be defined within control directives or other mixins."
122
121
  end
123
122
 
124
123
  VALID_FUNCTION_CHILDREN = [
@@ -126,27 +125,24 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
126
125
  Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::ErrorNode
127
126
  ] + CONTROL_NODES
128
127
  def invalid_function_child?(parent, child)
129
- unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
130
- "Functions can only contain variable declarations and control directives."
131
- end
128
+ return if is_any_of?(child, VALID_FUNCTION_CHILDREN)
129
+ "Functions can only contain variable declarations and control directives."
132
130
  end
133
131
 
134
- VALID_PROP_CHILDREN = CONTROL_NODES + [Sass::Tree::CommentNode,
135
- Sass::Tree::PropNode,
136
- Sass::Tree::MixinNode]
132
+ VALID_PROP_CHILDREN = CONTROL_NODES + [Sass::Tree::CommentNode,
133
+ Sass::Tree::PropNode,
134
+ Sass::Tree::MixinNode]
137
135
  def invalid_prop_child?(parent, child)
138
- unless is_any_of?(child, VALID_PROP_CHILDREN)
139
- "Illegal nesting: Only properties may be nested beneath properties."
140
- end
136
+ return if is_any_of?(child, VALID_PROP_CHILDREN)
137
+ "Illegal nesting: Only properties may be nested beneath properties."
141
138
  end
142
139
 
143
140
  VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::KeyframeRuleNode, Sass::Tree::PropNode,
144
141
  Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode, Sass::Tree::MixinNode]
145
142
  def invalid_prop_parent?(parent, child)
146
- unless is_any_of?(parent, VALID_PROP_PARENTS)
147
- "Properties are only allowed within rules, directives, mixin includes, or other properties." +
148
- child.pseudo_class_selector_message
149
- end
143
+ return if is_any_of?(parent, VALID_PROP_PARENTS)
144
+ "Properties are only allowed within rules, directives, mixin includes, or other properties." +
145
+ child.pseudo_class_selector_message
150
146
  end
151
147
 
152
148
  def invalid_return_parent?(parent, child)
@@ -18,15 +18,19 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
18
18
  @tabs = 0
19
19
  # 2 spaces by default
20
20
  @tab_chars = @options[:indent] || " "
21
+ @is_else = false
21
22
  end
22
23
 
23
24
  def visit_children(parent)
24
25
  @tabs += 1
25
26
  return @format == :sass ? "\n" : " {}\n" if parent.children.empty?
27
+
28
+ res = visit_rule_level(parent.children)
29
+
26
30
  if @format == :sass
27
- "\n" + super.join.rstrip + "\n"
31
+ "\n" + res.rstrip + "\n"
28
32
  else
29
- " {\n" + super.join.rstrip + "\n#{ @tab_chars * (@tabs - 1)}}\n"
33
+ " {\n" + res.rstrip + "\n#{@tab_chars * (@tabs - 1)}}\n"
30
34
  end
31
35
  ensure
32
36
  @tabs -= 1
@@ -34,20 +38,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
34
38
 
35
39
  # Ensures proper spacing between top-level nodes.
36
40
  def visit_root(node)
37
- Sass::Util.enum_cons(node.children + [nil], 2).map do |child, nxt|
38
- visit(child) +
39
- if nxt &&
40
- (child.is_a?(Sass::Tree::CommentNode) &&
41
- child.line + child.lines + 1 == nxt.line) ||
42
- (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
43
- child.line + 1 == nxt.line) ||
44
- (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
45
- child.line + 1 == nxt.line)
46
- ""
47
- else
48
- "\n"
49
- end
50
- end.join.rstrip + "\n"
41
+ visit_rule_level(node.children)
51
42
  end
52
43
 
53
44
  def visit_charset(node)
@@ -57,14 +48,14 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
57
48
  def visit_comment(node)
58
49
  value = interp_to_src(node.value)
59
50
  if @format == :sass
60
- content = value.gsub(/\*\/$/, '').rstrip
51
+ content = value.gsub(%r{\*/$}, '').rstrip
61
52
  if content =~ /\A[ \t]/
62
53
  # Re-indent SCSS comments like this:
63
54
  # /* foo
64
55
  # bar
65
56
  # baz */
66
57
  content.gsub!(/^/, ' ')
67
- content.sub!(/\A([ \t]*)\/\*/, '/*\1')
58
+ content.sub!(%r{\A([ \t]*)/\*}, '/*\1')
68
59
  end
69
60
 
70
61
  if content.include?("\n")
@@ -78,13 +69,13 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
78
69
  end
79
70
  end
80
71
 
81
- content.gsub!(/\A\/\*/, '//') if node.type == :silent
72
+ content.gsub!(%r{\A/\*}, '//') if node.type == :silent
82
73
  content.gsub!(/^/, tab_str)
83
74
  content = content.rstrip + "\n"
84
75
  else
85
76
  spaces = (@tab_chars * [@tabs - value[/^ */].size, 0].max)
86
77
  content = if node.type == :silent
87
- value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
78
+ value.gsub(%r{^[/ ]\*}, '//').gsub(%r{ *\*/$}, '')
88
79
  else
89
80
  value
90
81
  end.gsub(/^/, spaces) + "\n"
@@ -104,7 +95,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
104
95
  res = "#{tab_str}#{interp_to_src(node.value)}"
105
96
  res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2')
106
97
  return res + "#{semi}\n" unless node.has_children
107
- res + yield + "\n"
98
+ res + yield
108
99
  end
109
100
 
110
101
  def visit_each(node)
@@ -113,13 +104,13 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
113
104
  end
114
105
 
115
106
  def visit_extend(node)
116
- "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}#{semi}" +
117
- "#{" !optional" if node.optional?}\n"
107
+ "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}" +
108
+ "#{' !optional' if node.optional?}#{semi}\n"
118
109
  end
119
110
 
120
111
  def visit_for(node)
121
112
  "#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
122
- "#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
113
+ "#{node.exclusive ? 'to' : 'through'} #{node.to.to_sass(@options)}#{yield}"
123
114
  end
124
115
 
125
116
  def visit_function(node)
@@ -173,6 +164,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
173
164
  else
174
165
  str = "#{tab_str}@import #{node.uri}"
175
166
  end
167
+ str << " supports(#{node.supports_condition.to_src(@options)})" if node.supports_condition
176
168
  str << " #{interp_to_src(node.query)}" unless node.query.empty?
177
169
  "#{str}#{semi}\n"
178
170
  end
@@ -274,14 +266,39 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
274
266
  "#{tab_str}@at-root #{query_interp_to_src(node.query)}#{yield}"
275
267
  elsif node.children.length == 1 && node.children.first.is_a?(Sass::Tree::RuleNode)
276
268
  rule = node.children.first
277
- "#{tab_str}@at-root #{selector_to_src(rule.rule)}#{visit_children(rule)}"
269
+ "#{tab_str}@at-root #{selector_to_src(rule.rule).lstrip}#{visit_children(rule)}"
278
270
  else
279
271
  "#{tab_str}@at-root#{yield}"
280
272
  end
281
273
  end
282
274
 
275
+ def visit_keyframerule(node)
276
+ "#{tab_str}#{node.resolved_value}#{yield}"
277
+ end
278
+
283
279
  private
284
280
 
281
+ # Visit rule-level nodes and return their conversion with appropriate
282
+ # whitespace added.
283
+ def visit_rule_level(nodes)
284
+ Sass::Util.enum_cons(nodes + [nil], 2).map do |child, nxt|
285
+ visit(child) +
286
+ if nxt &&
287
+ (child.is_a?(Sass::Tree::CommentNode) && child.line + child.lines + 1 == nxt.line) ||
288
+ (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
289
+ child.line + 1 == nxt.line) ||
290
+ (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
291
+ child.line + 1 == nxt.line) ||
292
+ (child.is_a?(Sass::Tree::PropNode) && nxt.is_a?(Sass::Tree::PropNode)) ||
293
+ (child.is_a?(Sass::Tree::MixinNode) && nxt.is_a?(Sass::Tree::MixinNode) &&
294
+ child.line + 1 == nxt.line)
295
+ ""
296
+ else
297
+ "\n"
298
+ end
299
+ end.join.rstrip + "\n"
300
+ end
301
+
285
302
  def interp_to_src(interp)
286
303
  interp.map {|r| r.is_a?(String) ? r : r.to_sass(@options)}.join
287
304
  end
@@ -326,7 +343,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
326
343
 
327
344
  def dasherize(s)
328
345
  if @options[:dasherize]
329
- s.gsub('_', '-')
346
+ s.tr('_', '-')
330
347
  else
331
348
  s
332
349
  end
@@ -222,7 +222,11 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
222
222
  # Bubbles a directive up through RuleNodes.
223
223
  def visit_directive(node)
224
224
  return node unless node.has_children
225
- return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
225
+ if parent.is_a?(Sass::Tree::RuleNode)
226
+ # @keyframes shouldn't include the rule nodes, so we manually create a
227
+ # bubble that doesn't have the parent's contents for them.
228
+ return node.normalized_name == '@keyframes' ? Bubble.new(node) : bubble(node)
229
+ end
226
230
 
227
231
  yield
228
232
 
@@ -55,7 +55,7 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
55
55
 
56
56
  def visit_mixin(node)
57
57
  node.args = node.args.map {|a| a.deep_copy}
58
- node.keywords = Hash[node.keywords.map {|k, v| [k, v.deep_copy]}]
58
+ node.keywords = Sass::Util::NormalizedMap.new(Hash[node.keywords.map {|k, v| [k, v.deep_copy]}])
59
59
  yield
60
60
  end
61
61
 
@@ -44,25 +44,27 @@ class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
44
44
  node.resolved_rules = node.resolved_rules.do_extend(@extends, @parent_directives)
45
45
  end
46
46
 
47
- private
47
+ class << self
48
+ private
48
49
 
49
- def self.check_extends_fired!(extends)
50
- extends.each_value do |ex|
51
- next if ex.result == :succeeded || ex.node.optional?
52
- message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
53
- reason =
54
- if ex.result == :not_found
55
- "The selector \"#{ex.target.join}\" was not found."
56
- else
57
- "No selectors matching \"#{ex.target.join}\" could be unified with \"#{ex.extender}\"."
58
- end
50
+ def check_extends_fired!(extends)
51
+ extends.each_value do |ex|
52
+ next if ex.result == :succeeded || ex.node.optional?
53
+ message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
54
+ reason =
55
+ if ex.result == :not_found
56
+ "The selector \"#{ex.target.join}\" was not found."
57
+ else
58
+ "No selectors matching \"#{ex.target.join}\" could be unified with \"#{ex.extender}\"."
59
+ end
59
60
 
60
- # TODO(nweiz): this should use the Sass stack trace of the extend node.
61
- raise Sass::SyntaxError.new(<<MESSAGE, :filename => ex.node.filename, :line => ex.node.line)
61
+ # TODO(nweiz): this should use the Sass stack trace of the extend node.
62
+ raise Sass::SyntaxError.new(<<MESSAGE, :filename => ex.node.filename, :line => ex.node.line)
62
63
  #{message}
63
64
  #{reason}
64
65
  Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
65
66
  MESSAGE
67
+ end
66
68
  end
67
69
  end
68
70
  end
@@ -1,5 +1,7 @@
1
1
  # A visitor for converting a dynamic Sass tree into a static Sass tree.
2
2
  class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
3
+ @@function_name_deprecation = Sass::Deprecation.new
4
+
3
5
  class << self
4
6
  # @param root [Tree::Node] The root node of the tree to visit.
5
7
  # @param environment [Sass::Environment] The lexical environment.
@@ -11,7 +13,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
11
13
  # @api private
12
14
  # @comment
13
15
  # rubocop:disable MethodLength
14
- def perform_arguments(callable, args, splat)
16
+ def perform_arguments(callable, args, splat, environment)
15
17
  desc = "#{callable.type.capitalize} #{callable.name}"
16
18
  downcase_desc = "#{callable.type} #{callable.name}"
17
19
 
@@ -41,20 +43,26 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
41
43
  # raising happens in the ensure clause at the end of this function.
42
44
  return if keyword_exception && !callable.splat
43
45
 
44
- if args.size > callable.args.size && !callable.splat
45
- takes = callable.args.size
46
- passed = args.size
47
- raise Sass::SyntaxError.new(
48
- "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
49
- "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
50
- end
51
-
52
46
  splat_sep = :comma
53
47
  if splat
54
48
  args += splat.to_a
55
49
  splat_sep = splat.separator
56
50
  end
57
51
 
52
+ if args.size > callable.args.size && !callable.splat
53
+ extra_args_because_of_splat = splat && args.size - splat.to_a.size <= callable.args.size
54
+
55
+ takes = callable.args.size
56
+ passed = args.size
57
+ message = "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
58
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed."
59
+ raise Sass::SyntaxError.new(message) unless extra_args_because_of_splat
60
+ # TODO: when the deprecation period is over, make this an error.
61
+ Sass::Util.sass_warn("WARNING: #{message}\n" +
62
+ environment.stack.to_s.gsub(/^/m, " " * 8) + "\n" +
63
+ "This will be an error in future versions of Sass.")
64
+ end
65
+
58
66
  env = Sass::Environment.new(callable.environment)
59
67
  callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
60
68
  if value && keywords.has_key?(var.name)
@@ -144,6 +152,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
144
152
 
145
153
  def initialize(env)
146
154
  @environment = env
155
+ @in_keyframes = false
156
+ @at_root_without_rule = false
147
157
  end
148
158
 
149
159
  # If an exception is raised, this adds proper metadata to the backtrace.
@@ -222,7 +232,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
222
232
  def visit_each(node)
223
233
  list = node.list.perform(@environment)
224
234
 
225
- with_environment Sass::Environment.new(@environment) do
235
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
226
236
  list.to_a.map do |value|
227
237
  if node.vars.length == 1
228
238
  @environment.set_local_var(node.vars.first, value)
@@ -256,7 +266,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
256
266
  direction = from.to_i > to.to_i ? -1 : 1
257
267
  range = Range.new(direction * from.to_i, direction * to.to_i, node.exclusive)
258
268
 
259
- with_environment Sass::Environment.new(@environment) do
269
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
260
270
  range.map do |i|
261
271
  @environment.set_local_var(node.var,
262
272
  Sass::Script::Value::Number.new(direction * i,
@@ -269,9 +279,18 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
269
279
  # Loads the function into the environment.
270
280
  def visit_function(node)
271
281
  env = Sass::Environment.new(@environment, node.options)
282
+
283
+ if node.normalized_name == 'calc' || node.normalized_name == 'element' ||
284
+ node.name == 'expression' || node.name == 'url'
285
+ @@function_name_deprecation.warn(node.filename, node.line, <<WARNING)
286
+ Naming a function "#{node.name}" is disallowed and will be an error in future versions of Sass.
287
+ This name conflicts with an existing CSS function with special parse rules.
288
+ WARNING
289
+ end
290
+
272
291
  @environment.set_local_function(node.name,
273
292
  Sass::Callable.new(node.name, node.args, node.splat, env,
274
- node.children, !:has_content, "function"))
293
+ node.children, false, "function"))
275
294
  []
276
295
  end
277
296
 
@@ -279,8 +298,9 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
279
298
  # otherwise, tries the else nodes.
280
299
  def visit_if(node)
281
300
  if node.expr.nil? || node.expr.perform(@environment).to_bool
282
- yield
283
- node.children
301
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
302
+ node.children.map {|c| visit(c)}
303
+ end.flatten
284
304
  elsif node.else
285
305
  visit(node.else)
286
306
  else
@@ -293,6 +313,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
293
313
  def visit_import(node)
294
314
  if (path = node.css_import?)
295
315
  resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
316
+ resolved_node.options = node.options
296
317
  resolved_node.source_range = node.source_range
297
318
  return resolved_node
298
319
  end
@@ -331,14 +352,14 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
331
352
  raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
332
353
 
333
354
  if node.children.any? && !mixin.has_content
334
- raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
355
+ raise Sass::SyntaxError.new(%(Mixin "#{node.name}" does not accept a content block.))
335
356
  end
336
357
 
337
358
  args = node.args.map {|a| a.perform(@environment)}
338
359
  keywords = Sass::Util.map_vals(node.keywords) {|v| v.perform(@environment)}
339
360
  splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment)
340
361
 
341
- self.class.perform_arguments(mixin, args, splat) do |env|
362
+ self.class.perform_arguments(mixin, args, splat, @environment) do |env|
342
363
  env.caller = Sass::Environment.new(@environment)
343
364
  env.content = [node.children, @environment] if node.has_children
344
365
 
@@ -397,6 +418,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
397
418
  keyframe_rule_node.line = node.line
398
419
  keyframe_rule_node.filename = node.filename
399
420
  keyframe_rule_node.source_range = node.source_range
421
+ keyframe_rule_node.has_children = node.has_children
400
422
  with_environment Sass::Environment.new(@environment, node.options) do
401
423
  keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten
402
424
  end
@@ -470,7 +492,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
470
492
  # Runs the child nodes until the continuation expression becomes false.
471
493
  def visit_while(node)
472
494
  children = []
473
- with_environment Sass::Environment.new(@environment) do
495
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
474
496
  children += node.children.map {|c| visit(c)} while node.expr.perform(@environment).to_bool
475
497
  end
476
498
  children.flatten
@@ -507,6 +529,9 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
507
529
  node.filename, node.options[:importer], node.line)
508
530
  node.resolved_query ||= parser.parse_media_query_list
509
531
  end
532
+ if node.supports_condition
533
+ node.supports_condition.perform(@environment)
534
+ end
510
535
  yield
511
536
  end
512
537
 
@@ -80,7 +80,7 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
80
80
 
81
81
  def visit_mixin(node)
82
82
  node.args.each {|a| a.options = @options}
83
- node.keywords.each {|k, v| v.options = @options}
83
+ node.keywords.each {|_k, v| v.options = @options}
84
84
  node.splat.options = @options if node.splat
85
85
  node.kwarg_splat.options = @options if node.kwarg_splat
86
86
  yield