sass 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +58 -50
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/bin/sass +1 -1
  9. data/bin/scss +1 -1
  10. data/lib/sass/cache_stores/filesystem.rb +6 -2
  11. data/lib/sass/css.rb +1 -3
  12. data/lib/sass/engine.rb +37 -46
  13. data/lib/sass/environment.rb +13 -17
  14. data/lib/sass/error.rb +6 -9
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +424 -0
  18. data/lib/sass/exec.rb +5 -771
  19. data/lib/sass/features.rb +7 -0
  20. data/lib/sass/importers/base.rb +7 -2
  21. data/lib/sass/importers/filesystem.rb +9 -25
  22. data/lib/sass/importers.rb +0 -1
  23. data/lib/sass/media.rb +1 -4
  24. data/lib/sass/plugin/compiler.rb +200 -83
  25. data/lib/sass/plugin/staleness_checker.rb +1 -1
  26. data/lib/sass/plugin.rb +3 -3
  27. data/lib/sass/script/css_lexer.rb +1 -1
  28. data/lib/sass/script/functions.rb +622 -268
  29. data/lib/sass/script/lexer.rb +99 -34
  30. data/lib/sass/script/parser.rb +24 -23
  31. data/lib/sass/script/tree/funcall.rb +1 -1
  32. data/lib/sass/script/tree/interpolation.rb +20 -2
  33. data/lib/sass/script/tree/selector.rb +26 -0
  34. data/lib/sass/script/tree/string_interpolation.rb +1 -1
  35. data/lib/sass/script/tree.rb +1 -0
  36. data/lib/sass/script/value/base.rb +7 -5
  37. data/lib/sass/script/value/bool.rb +0 -5
  38. data/lib/sass/script/value/color.rb +39 -21
  39. data/lib/sass/script/value/helpers.rb +107 -0
  40. data/lib/sass/script/value/list.rb +0 -15
  41. data/lib/sass/script/value/null.rb +0 -5
  42. data/lib/sass/script/value/number.rb +62 -14
  43. data/lib/sass/script/value/string.rb +59 -11
  44. data/lib/sass/script/value.rb +0 -1
  45. data/lib/sass/scss/css_parser.rb +8 -2
  46. data/lib/sass/scss/parser.rb +190 -328
  47. data/lib/sass/scss/rx.rb +15 -6
  48. data/lib/sass/scss/static_parser.rb +298 -1
  49. data/lib/sass/selector/abstract_sequence.rb +28 -13
  50. data/lib/sass/selector/comma_sequence.rb +92 -13
  51. data/lib/sass/selector/pseudo.rb +256 -0
  52. data/lib/sass/selector/sequence.rb +94 -24
  53. data/lib/sass/selector/simple.rb +14 -25
  54. data/lib/sass/selector/simple_sequence.rb +97 -33
  55. data/lib/sass/selector.rb +57 -194
  56. data/lib/sass/shared.rb +1 -1
  57. data/lib/sass/source/map.rb +26 -12
  58. data/lib/sass/stack.rb +0 -6
  59. data/lib/sass/supports.rb +2 -3
  60. data/lib/sass/tree/at_root_node.rb +1 -0
  61. data/lib/sass/tree/charset_node.rb +1 -1
  62. data/lib/sass/tree/directive_node.rb +8 -2
  63. data/lib/sass/tree/error_node.rb +18 -0
  64. data/lib/sass/tree/extend_node.rb +1 -1
  65. data/lib/sass/tree/function_node.rb +4 -0
  66. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  67. data/lib/sass/tree/prop_node.rb +1 -1
  68. data/lib/sass/tree/rule_node.rb +12 -7
  69. data/lib/sass/tree/visitors/check_nesting.rb +38 -10
  70. data/lib/sass/tree/visitors/convert.rb +16 -18
  71. data/lib/sass/tree/visitors/cssize.rb +29 -29
  72. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  73. data/lib/sass/tree/visitors/perform.rb +45 -33
  74. data/lib/sass/tree/visitors/set_options.rb +14 -0
  75. data/lib/sass/tree/visitors/to_css.rb +15 -14
  76. data/lib/sass/util/subset_map.rb +1 -1
  77. data/lib/sass/util.rb +222 -99
  78. data/lib/sass/version.rb +5 -5
  79. data/lib/sass.rb +0 -5
  80. data/test/sass/cache_test.rb +62 -20
  81. data/test/sass/callbacks_test.rb +1 -1
  82. data/test/sass/compiler_test.rb +19 -10
  83. data/test/sass/conversion_test.rb +58 -1
  84. data/test/sass/css2sass_test.rb +23 -4
  85. data/test/sass/encoding_test.rb +219 -0
  86. data/test/sass/engine_test.rb +136 -199
  87. data/test/sass/exec_test.rb +2 -2
  88. data/test/sass/extend_test.rb +236 -19
  89. data/test/sass/functions_test.rb +295 -253
  90. data/test/sass/importer_test.rb +31 -21
  91. data/test/sass/logger_test.rb +1 -1
  92. data/test/sass/more_results/more_import.css +1 -1
  93. data/test/sass/plugin_test.rb +14 -13
  94. data/test/sass/results/compact.css +1 -1
  95. data/test/sass/results/complex.css +4 -4
  96. data/test/sass/results/expanded.css +1 -1
  97. data/test/sass/results/import.css +1 -1
  98. data/test/sass/results/import_charset_ibm866.css +2 -2
  99. data/test/sass/results/mixins.css +17 -17
  100. data/test/sass/results/nested.css +1 -1
  101. data/test/sass/results/parent_ref.css +2 -2
  102. data/test/sass/results/script.css +3 -3
  103. data/test/sass/results/scss_import.css +1 -1
  104. data/test/sass/script_conversion_test.rb +10 -7
  105. data/test/sass/script_test.rb +288 -74
  106. data/test/sass/scss/css_test.rb +141 -24
  107. data/test/sass/scss/rx_test.rb +4 -4
  108. data/test/sass/scss/scss_test.rb +457 -18
  109. data/test/sass/source_map_test.rb +115 -25
  110. data/test/sass/superselector_test.rb +191 -0
  111. data/test/sass/templates/scss_import.scss +2 -1
  112. data/test/sass/test_helper.rb +1 -1
  113. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  114. data/test/sass/util/normalized_map_test.rb +1 -1
  115. data/test/sass/util/subset_map_test.rb +2 -2
  116. data/test/sass/util_test.rb +31 -1
  117. data/test/sass/value_helpers_test.rb +5 -7
  118. data/test/test_helper.rb +2 -2
  119. data/vendor/listen/CHANGELOG.md +1 -228
  120. data/vendor/listen/Gemfile +5 -15
  121. data/vendor/listen/README.md +111 -77
  122. data/vendor/listen/Rakefile +0 -42
  123. data/vendor/listen/lib/listen/adapter.rb +195 -82
  124. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  125. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  126. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  127. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  128. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  129. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  130. data/vendor/listen/lib/listen/listener.rb +135 -37
  131. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  132. data/vendor/listen/lib/listen/version.rb +1 -1
  133. data/vendor/listen/lib/listen.rb +33 -19
  134. data/vendor/listen/listen.gemspec +6 -0
  135. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  136. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  137. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  138. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  139. data/vendor/listen/spec/listen_spec.rb +15 -21
  140. data/vendor/listen/spec/spec_helper.rb +4 -0
  141. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  142. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  143. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  144. metadata +25 -22
  145. data/ext/mkrf_conf.rb +0 -27
  146. data/lib/sass/importers/deprecated_path.rb +0 -51
  147. data/lib/sass/script/value/deprecated_false.rb +0 -55
  148. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  149. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  150. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  151. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -68,12 +68,6 @@ module Sass::Source
68
68
  # it will be inferred from `:css_path` and `:sourcemap_path` using the
69
69
  # assumption that the local file system has the same layout as the server.
70
70
  #
71
- # If any source stylesheets use the default filesystem importer, sourcemap
72
- # generation will fail unless the `:sourcemap_path` option is specified.
73
- # The layout of the local file system is assumed to be the same as the
74
- # layout of the server for the purposes of linking to source stylesheets
75
- # that use the filesystem importer.
76
- #
77
71
  # Regardless of which options are passed to this method, source stylesheets
78
72
  # that are imported using a non-default importer will only be linked to in
79
73
  # the source map if their importers implement
@@ -85,6 +79,8 @@ module Sass::Source
85
79
  # The local path of the CSS output file.
86
80
  # @option options :sourcemap_path [String]
87
81
  # The (eventual) local path of the sourcemap file.
82
+ # @option options :type [Symbol]
83
+ # `:auto` (default), `:file`, or `:inline`.
88
84
  # @return [String] The JSON string.
89
85
  # @raise [ArgumentError] If neither `:css_uri` nor `:css_path` and
90
86
  # `:sourcemap_path` are specified.
@@ -97,15 +93,16 @@ module Sass::Source
97
93
  raise ArgumentError.new("Sass::Source::Map#to_json requires either " \
98
94
  "the :css_uri option or both the :css_path and :soucemap_path options.")
99
95
  end
100
- css_path &&= Pathname.pwd.join(Sass::Util.pathname(css_path)).cleanpath
101
- sourcemap_path &&= Pathname.pwd.join(Sass::Util.pathname(sourcemap_path)).cleanpath
102
- css_uri ||= css_path.relative_path_from(sourcemap_path.dirname).to_s
96
+ css_path &&= Sass::Util.pathname(Sass::Util.absolute_path(css_path))
97
+ sourcemap_path &&= Sass::Util.pathname(Sass::Util.absolute_path(sourcemap_path))
98
+ css_uri ||= Sass::Util.file_uri_from_path(css_path.relative_path_from(sourcemap_path.dirname))
103
99
 
104
100
  result = "{\n"
105
101
  write_json_field(result, "version", 3, true)
106
102
 
107
103
  source_uri_to_id = {}
108
104
  id_to_source_uri = {}
105
+ id_to_contents = {} if options[:type] == :inline
109
106
  next_source_id = 0
110
107
  line_data = []
111
108
  segment_data_for_line = []
@@ -119,9 +116,15 @@ module Sass::Source
119
116
 
120
117
  @data.each do |m|
121
118
  file, importer = m.input.file, m.input.importer
122
- source_uri = importer &&
123
- importer.public_url(file, sourcemap_path && sourcemap_path.dirname.to_s)
124
- next unless source_uri
119
+
120
+ if options[:type] == :inline
121
+ source_uri = file
122
+ else
123
+ sourcemap_dir = sourcemap_path && sourcemap_path.dirname.to_s
124
+ sourcemap_dir = nil if options[:type] == :file
125
+ source_uri = importer && importer.public_url(file, sourcemap_dir)
126
+ next unless source_uri
127
+ end
125
128
 
126
129
  current_source_id = source_uri_to_id[source_uri]
127
130
  unless current_source_id
@@ -130,6 +133,11 @@ module Sass::Source
130
133
 
131
134
  source_uri_to_id[source_uri] = current_source_id
132
135
  id_to_source_uri[current_source_id] = source_uri
136
+
137
+ if options[:type] == :inline
138
+ id_to_contents[current_source_id] =
139
+ importer.find(file, {}).instance_variable_get('@template')
140
+ end
133
141
  end
134
142
 
135
143
  [
@@ -174,6 +182,12 @@ module Sass::Source
174
182
  source_names = []
175
183
  (0...next_source_id).each {|id| source_names.push(id_to_source_uri[id].to_s)}
176
184
  write_json_field(result, "sources", source_names)
185
+
186
+ if options[:type] == :inline
187
+ write_json_field(result, "sourcesContent",
188
+ (0...next_source_id).map {|id| id_to_contents[id]})
189
+ end
190
+
177
191
  write_json_field(result, "names", [])
178
192
  write_json_field(result, "file", css_uri)
179
193
 
data/lib/sass/stack.rb CHANGED
@@ -107,12 +107,6 @@ module Sass
107
107
  end.join("\n")
108
108
  end
109
109
 
110
- def deep_copy
111
- stack = Stack.new
112
- stack.frames.replace frames
113
- stack
114
- end
115
-
116
110
  private
117
111
 
118
112
  def with_frame(filename, line, type, name = nil)
data/lib/sass/supports.rb CHANGED
@@ -203,8 +203,7 @@ module Sass::Supports
203
203
  end
204
204
 
205
205
  def perform(env)
206
- val = value.perform(env)
207
- @resolved_value = val.is_a?(Sass::Script::Value::String) ? val.value : val.to_s
206
+ @resolved_value = value.perform(env).to_s(:quote => :none)
208
207
  end
209
208
 
210
209
  def to_css
@@ -212,7 +211,7 @@ module Sass::Supports
212
211
  end
213
212
 
214
213
  def to_src(options)
215
- "\#{#{@value.to_sass(options)}}"
214
+ @value.to_sass(options)
216
215
  end
217
216
 
218
217
  def deep_copy
@@ -70,6 +70,7 @@ module Sass
70
70
  # @return [Boolean]
71
71
  def exclude_node?(node)
72
72
  return exclude?(node.name.gsub(/^@/, '')) if node.is_a?(Sass::Tree::DirectiveNode)
73
+ return exclude?('keyframes') if node.is_a?(Sass::Tree::KeyframeRuleNode)
73
74
  exclude?('rule') && node.is_a?(Sass::Tree::RuleNode)
74
75
  end
75
76
 
@@ -1,5 +1,5 @@
1
1
  module Sass::Tree
2
- # A static node representing an unproccessed Sass `@charset` directive.
2
+ # A static node representing an unprocessed Sass `@charset` directive.
3
3
  #
4
4
  # @see Sass::Tree
5
5
  class CharsetNode < Node
@@ -1,5 +1,5 @@
1
1
  module Sass::Tree
2
- # A static node representing an unproccessed Sass `@`-directive.
2
+ # A static node representing an unprocessed Sass `@`-directive.
3
3
  # Directives known to Sass, like `@for` and `@debug`,
4
4
  # are handled by their own nodes;
5
5
  # only CSS directives like `@media` and `@font-face` become {DirectiveNode}s.
@@ -43,7 +43,13 @@ module Sass::Tree
43
43
 
44
44
  # @return [String] The name of the directive, including `@`.
45
45
  def name
46
- value.first.gsub(/ .*$/, '')
46
+ @name ||= value.first.gsub(/ .*$/, '')
47
+ end
48
+
49
+ # Strips out any vendor prefixes and downcases the directive name.
50
+ # @return [String] The normalized name of the directive.
51
+ def normalized_name
52
+ @normalized_name ||= name.gsub(/^(@)(?:-[a-zA-Z0-9]+-)?/, '\1').downcase
47
53
  end
48
54
 
49
55
  def bubbles?
@@ -0,0 +1,18 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing a Sass `@error` statement.
4
+ #
5
+ # @see Sass::Tree
6
+ class ErrorNode < Node
7
+ # The expression to print.
8
+ # @return [Script::Tree::Node]
9
+ attr_accessor :expr
10
+
11
+ # @param expr [Script::Tree::Node] The expression to print
12
+ def initialize(expr)
13
+ @expr = expr
14
+ super()
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,7 +1,7 @@
1
1
  require 'sass/tree/node'
2
2
 
3
3
  module Sass::Tree
4
- # A static node reprenting an `@extend` directive.
4
+ # A static node representing an `@extend` directive.
5
5
  #
6
6
  # @see Sass::Tree
7
7
  class ExtendNode < Node
@@ -29,6 +29,10 @@ module Sass
29
29
  @args = args
30
30
  @splat = splat
31
31
  super()
32
+
33
+ if %w[and or not].include?(name)
34
+ raise Sass::SyntaxError.new("Invalid function name \"#{name}\".")
35
+ end
32
36
  end
33
37
  end
34
38
  end
@@ -0,0 +1,15 @@
1
+ module Sass::Tree
2
+ class KeyframeRuleNode < Node
3
+ # The text of the directive after any interpolated SassScript has been resolved.
4
+ # Since this is only a static node, this is the only value property.
5
+ #
6
+ # @return [String]
7
+ attr_accessor :resolved_value
8
+
9
+ # @param resolved_value [String] See \{#resolved_value}
10
+ def initialize(resolved_value)
11
+ @resolved_value = resolved_value
12
+ super()
13
+ end
14
+ end
15
+ end
@@ -97,7 +97,7 @@ module Sass::Tree
97
97
  # @param opts [{Symbol => Object}] The options hash for the tree.
98
98
  # @param fmt [Symbol] `:scss` or `:sass`.
99
99
  def declaration(opts = {:old => @prop_syntax == :old}, fmt = :sass)
100
- name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass(opts)}}"}.join
100
+ name = self.name.map {|n| n.is_a?(String) ? n : n.to_sass(opts)}.join
101
101
  if name[0] == ?:
102
102
  raise Sass::SyntaxError.new("The \"#{name}: #{self.class.val_to_sass(value, opts)}\"" +
103
103
  " hack is not allowed in the Sass indented syntax")
@@ -1,7 +1,7 @@
1
1
  require 'pathname'
2
2
 
3
3
  module Sass::Tree
4
- # A static node reprenting a CSS rule.
4
+ # A static node representing a CSS rule.
5
5
  #
6
6
  # @see Sass::Tree
7
7
  class RuleNode < Node
@@ -60,15 +60,20 @@ module Sass::Tree
60
60
  # @return [String]
61
61
  attr_accessor :stack_trace
62
62
 
63
- # @param rule [Array<String, Sass::Script::Tree::Node>]
63
+ # @param rule [Array<String, Sass::Script::Tree::Node>, Sass::Selector::CommaSequence]
64
+ # The CSS rule, either unparsed or parsed.
64
65
  # @param selector_source_range [Sass::Source::Range]
65
- # The CSS rule. See \{#rule}
66
66
  def initialize(rule, selector_source_range = nil)
67
- merged = Sass::Util.merge_adjacent_strings(rule)
68
- @rule = Sass::Util.strip_string_array(merged)
67
+ if rule.is_a?(Sass::Selector::CommaSequence)
68
+ @rule = [rule.to_s]
69
+ @parsed_rules = rule
70
+ else
71
+ merged = Sass::Util.merge_adjacent_strings(rule)
72
+ @rule = Sass::Util.strip_string_array(merged)
73
+ try_to_parse_non_interpolated_rules
74
+ end
69
75
  @selector_source_range = selector_source_range
70
76
  @tabs = 0
71
- try_to_parse_non_interpolated_rules
72
77
  super()
73
78
  end
74
79
 
@@ -130,7 +135,7 @@ module Sass::Tree
130
135
  if @rule.all? {|t| t.kind_of?(String)}
131
136
  # We don't use real filename/line info because we don't have it yet.
132
137
  # When we get it, we'll set it on the parsed rules if possible.
133
- parser = Sass::SCSS::StaticParser.new(@rule.join.strip, '', nil, 1)
138
+ parser = Sass::SCSS::StaticParser.new(@rule.join.strip, nil, nil, 1)
134
139
  # rubocop:disable RescueModifier
135
140
  @parsed_rules = parser.parse_selector rescue nil
136
141
  # rubocop:enable RescueModifier
@@ -23,13 +23,34 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
23
23
  SCRIPT_NODES = [Sass::Tree::ImportNode] + CONTROL_NODES
24
24
  def visit_children(parent)
25
25
  old_parent = @parent
26
- @parent = parent unless is_any_of?(parent, SCRIPT_NODES) ||
27
- (parent.bubbles? && !old_parent.is_a?(Sass::Tree::RootNode))
26
+
27
+ # When checking a static tree, resolve at-roots to be sure they won't send
28
+ # nodes where they don't belong.
29
+ if parent.is_a?(Sass::Tree::AtRootNode) && parent.resolved_value
30
+ old_parents = @parents
31
+ @parents = @parents.reject {|p| parent.exclude_node?(p)}
32
+ @parent = Sass::Util.enum_with_index(@parents.reverse).
33
+ find {|p, i| !transparent_parent?(p, @parents[-i - 2])}.first
34
+
35
+ begin
36
+ return super
37
+ ensure
38
+ @parents = old_parents
39
+ @parent = old_parent
40
+ end
41
+ end
42
+
43
+ unless transparent_parent?(parent, old_parent)
44
+ @parent = parent
45
+ end
46
+
28
47
  @parents.push parent
29
- super
30
- ensure
31
- @parent = old_parent
32
- @parents.pop
48
+ begin
49
+ super
50
+ ensure
51
+ @parent = old_parent
52
+ @parents.pop
53
+ end
33
54
  end
34
55
 
35
56
  def visit_root(node)
@@ -102,7 +123,7 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
102
123
 
103
124
  VALID_FUNCTION_CHILDREN = [
104
125
  Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
105
- Sass::Tree::VariableNode, Sass::Tree::WarnNode
126
+ Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::ErrorNode
106
127
  ] + CONTROL_NODES
107
128
  def invalid_function_child?(parent, child)
108
129
  unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
@@ -119,9 +140,8 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
119
140
  end
120
141
  end
121
142
 
122
- VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::PropNode,
123
- Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode,
124
- Sass::Tree::MixinNode]
143
+ VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::KeyframeRuleNode, Sass::Tree::PropNode,
144
+ Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode, Sass::Tree::MixinNode]
125
145
  def invalid_prop_parent?(parent, child)
126
146
  unless is_any_of?(parent, VALID_PROP_PARENTS)
127
147
  "Properties are only allowed within rules, directives, mixin includes, or other properties." +
@@ -135,6 +155,14 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
135
155
 
136
156
  private
137
157
 
158
+ # Whether `parent` should be assigned to `@parent`.
159
+ def transparent_parent?(parent, grandparent)
160
+ is_any_of?(parent, SCRIPT_NODES) ||
161
+ (parent.bubbles? &&
162
+ !grandparent.is_a?(Sass::Tree::RootNode) &&
163
+ !grandparent.is_a?(Sass::Tree::AtRootNode))
164
+ end
165
+
138
166
  def is_any_of?(val, classes)
139
167
  classes.each do |c|
140
168
  return true if val.is_a?(c)
@@ -96,6 +96,10 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
96
96
  "#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"
97
97
  end
98
98
 
99
+ def visit_error(node)
100
+ "#{tab_str}@error #{node.expr.to_sass(@options)}#{semi}\n"
101
+ end
102
+
99
103
  def visit_directive(node)
100
104
  res = "#{tab_str}#{interp_to_src(node.value)}"
101
105
  res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2')
@@ -236,12 +240,13 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
236
240
  end
237
241
 
238
242
  def visit_rule(node)
243
+ rule = node.parsed_rules ? [node.parsed_rules.to_s] : node.rule
239
244
  if @format == :sass
240
- name = selector_to_sass(node.rule)
245
+ name = selector_to_sass(rule)
241
246
  name = "\\" + name if name[0] == ?:
242
247
  name.gsub(/^/, tab_str) + yield
243
248
  elsif @format == :scss
244
- name = selector_to_scss(node.rule)
249
+ name = selector_to_scss(rule)
245
250
  res = name + yield
246
251
  if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent
247
252
  res.slice!(-3..-1)
@@ -278,26 +283,19 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
278
283
  private
279
284
 
280
285
  def interp_to_src(interp)
281
- interp.map do |r|
282
- next r if r.is_a?(String)
283
- "\#{#{r.to_sass(@options)}}"
284
- end.join
286
+ interp.map {|r| r.is_a?(String) ? r : r.to_sass(@options)}.join
285
287
  end
286
288
 
287
289
  # Like interp_to_src, but removes the unnecessary `#{}` around the keys and
288
290
  # values in query expressions.
289
291
  def query_interp_to_src(interp)
290
- Sass::Util.enum_with_index(interp).map do |r, i|
291
- next r if r.is_a?(String)
292
- before, after = interp[i - 1], interp[i + 1]
293
- if before.is_a?(String) && after.is_a?(String) &&
294
- ((before[-1] == ?( && after[0] == ?:) ||
295
- (before =~ /:\s*/ && after[0] == ?)))
296
- r.to_sass(@options)
297
- else
298
- "\#{#{r.to_sass(@options)}}"
299
- end
300
- end.join
292
+ interp = interp.map do |e|
293
+ next e unless e.is_a?(Sass::Script::Tree::Literal)
294
+ next e unless e.value.is_a?(Sass::Script::Value::String)
295
+ e.value.value
296
+ end
297
+
298
+ interp_to_src(interp)
301
299
  end
302
300
 
303
301
  def selector_to_src(sel)
@@ -309,7 +307,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
309
307
  if r.is_a?(String)
310
308
  r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
311
309
  else
312
- "\#{#{r.to_sass(@options)}}"
310
+ r.to_sass(@options)
313
311
  end
314
312
  end.join
315
313
  end
@@ -123,29 +123,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
123
123
 
124
124
  # Registers an extension in the `@extends` subset map.
125
125
  def visit_extend(node)
126
- node.resolved_selector.members.each do |seq|
127
- if seq.members.size > 1
128
- raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
129
- end
130
-
131
- sseq = seq.members.first
132
- if !sseq.is_a?(Sass::Selector::SimpleSequence)
133
- raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
134
- elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
135
- raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
136
- end
137
-
138
- sel = sseq.members
139
- parent.resolved_rules.members.each do |member|
140
- unless member.members.last.is_a?(Sass::Selector::SimpleSequence)
141
- raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
142
- end
143
-
144
- parent_directives = @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)}
145
- @extends[sel] = Extend.new(member, sel, node, parent_directives, :not_found)
146
- end
147
- end
148
-
126
+ parent.resolved_rules.populate_extends(@extends, node.resolved_selector, node,
127
+ @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)})
149
128
  []
150
129
  end
151
130
 
@@ -232,6 +211,14 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
232
211
  rules
233
212
  end
234
213
 
214
+ def visit_keyframerule(node)
215
+ return node unless node.has_children
216
+
217
+ yield
218
+
219
+ debubble(node.children, node)
220
+ end
221
+
235
222
  # Bubbles a directive up through RuleNodes.
236
223
  def visit_directive(node)
237
224
  return node unless node.has_children
@@ -249,7 +236,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
249
236
  child.node.resolved_value == node.resolved_value
250
237
  end
251
238
 
252
- if directive_exists
239
+ # We know empty @keyframes directives do nothing.
240
+ if directive_exists || node.name == '@keyframes'
253
241
  []
254
242
  else
255
243
  empty_node = node.dup
@@ -315,19 +303,31 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
315
303
  # omitted.
316
304
  # @return [List<Sass::Tree::Node, Bubble>]
317
305
  def debubble(children, parent = nil)
306
+ # Keep track of the previous parent so that we don't divide `parent`
307
+ # unnecessarily if the `@at-root` doesn't produce any new nodes (e.g.
308
+ # `@at-root {@extend %foo}`).
309
+ previous_parent = nil
310
+
318
311
  Sass::Util.slice_by(children) {|c| c.is_a?(Bubble)}.map do |(is_bubble, slice)|
319
312
  unless is_bubble
320
313
  next slice unless parent
321
- new_parent = parent.dup
322
- new_parent.children = slice
323
- next new_parent
314
+ if previous_parent
315
+ previous_parent.children.push(*slice)
316
+ next []
317
+ else
318
+ previous_parent = new_parent = parent.dup
319
+ new_parent.children = slice
320
+ next new_parent
321
+ end
324
322
  end
325
323
 
326
- next slice.map do |bubble|
324
+ slice.map do |bubble|
327
325
  next unless (node = block_given? ? yield(bubble.node) : bubble.node)
328
326
  node.tabs += bubble.tabs
329
327
  node.group_end = bubble.group_end
330
- [visit(node)].flatten
328
+ results = [visit(node)].flatten
329
+ previous_parent = nil unless results.empty?
330
+ results
331
331
  end.compact
332
332
  end.flatten
333
333
  end
@@ -16,6 +16,11 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
16
16
  yield
17
17
  end
18
18
 
19
+ def visit_error(node)
20
+ node.expr = node.expr.deep_copy
21
+ yield
22
+ end
23
+
19
24
  def visit_each(node)
20
25
  node.list = node.list.deep_copy
21
26
  yield
@@ -207,6 +207,17 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
207
207
  []
208
208
  end
209
209
 
210
+ # Throws the expression as an error.
211
+ def visit_error(node)
212
+ res = node.expr.perform(@environment)
213
+ if res.is_a?(Sass::Script::Value::String)
214
+ res = res.value
215
+ else
216
+ res = res.to_sass
217
+ end
218
+ raise Sass::SyntaxError.new(res)
219
+ end
220
+
210
221
  # Runs the child nodes once for each value in the list.
211
222
  def visit_each(node)
212
223
  list = node.list.perform(@environment)
@@ -377,18 +388,31 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
377
388
  # Runs SassScript interpolation in the selector,
378
389
  # and then parses the result into a {Sass::Selector::CommaSequence}.
379
390
  def visit_rule(node)
380
- old_at_root_without_rule, @at_root_without_rule = @at_root_without_rule, false
391
+ old_at_root_without_rule = @at_root_without_rule
381
392
  parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
382
393
  node.filename, node.options[:importer], node.line)
383
- node.parsed_rules ||= parser.parse_selector
384
- node.resolved_rules = node.parsed_rules.resolve_parent_refs(
385
- @environment.selector, !old_at_root_without_rule)
386
- node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
387
- with_environment Sass::Environment.new(@environment, node.options) do
388
- @environment.selector = node.resolved_rules
389
- node.children = node.children.map {|c| visit(c)}.flatten
394
+ if @in_keyframes
395
+ keyframe_rule_node = Sass::Tree::KeyframeRuleNode.new(parser.parse_keyframes_selector)
396
+ keyframe_rule_node.options = node.options
397
+ keyframe_rule_node.line = node.line
398
+ keyframe_rule_node.filename = node.filename
399
+ keyframe_rule_node.source_range = node.source_range
400
+ with_environment Sass::Environment.new(@environment, node.options) do
401
+ keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten
402
+ end
403
+ keyframe_rule_node
404
+ else
405
+ @at_root_without_rule = false
406
+ node.parsed_rules ||= parser.parse_selector
407
+ node.resolved_rules = node.parsed_rules.resolve_parent_refs(
408
+ @environment.selector, !old_at_root_without_rule)
409
+ node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
410
+ with_environment Sass::Environment.new(@environment, node.options) do
411
+ @environment.selector = node.resolved_rules
412
+ node.children = node.children.map {|c| visit(c)}.flatten
413
+ end
414
+ node
390
415
  end
391
- node
392
416
  ensure
393
417
  @at_root_without_rule = old_at_root_without_rule
394
418
  end
@@ -405,36 +429,24 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
405
429
  end
406
430
 
407
431
  old_at_root_without_rule = @at_root_without_rule
432
+ old_in_keyframes = @in_keyframes
408
433
  @at_root_without_rule = true if node.exclude?('rule')
434
+ @in_keyframes = false if node.exclude?('keyframes')
409
435
  yield
410
436
  ensure
437
+ @in_keyframes = old_in_keyframes
411
438
  @at_root_without_rule = old_at_root_without_rule
412
439
  end
413
440
 
414
441
  # Loads the new variable value into the environment.
415
442
  def visit_variable(node)
416
443
  env = @environment
417
- identifier = [node.name, node.filename, node.line]
418
- if node.global
419
- env = env.global_env
420
- elsif env.parent && env.is_var_global?(node.name) &&
421
- !env.global_env.global_warning_given.include?(identifier)
422
- env.global_env.global_warning_given.add(identifier)
423
- var_expr = "$#{node.name}: #{node.expr.to_sass(env.options)} !global"
424
- var_expr << " !default" if node.guarded
425
- location = "on line #{node.line}"
426
- location << " of #{node.filename}" if node.filename
427
- Sass::Util.sass_warn <<WARNING
428
- DEPRECATION WARNING #{location}:
429
- Assigning to global variable "$#{node.name}" by default is deprecated.
430
- In future versions of Sass, this will create a new local variable.
431
- If you want to assign to the global variable, use "#{var_expr}" instead.
432
- Note that this will be incompatible with Sass 3.2.
433
- WARNING
444
+ env = env.global_env if node.global
445
+ if node.guarded
446
+ var = env.var(node.name)
447
+ return [] if var && !var.null?
434
448
  end
435
449
 
436
- var = env.var(node.name)
437
- return [] if node.guarded && var && !var.null?
438
450
  val = node.expr.perform(@environment)
439
451
  if node.expr.source_range
440
452
  val.source_range = node.expr.source_range
@@ -466,10 +478,13 @@ WARNING
466
478
 
467
479
  def visit_directive(node)
468
480
  node.resolved_value = run_interp(node.value)
481
+ old_in_keyframes, @in_keyframes = @in_keyframes, node.normalized_name == "@keyframes"
469
482
  with_environment Sass::Environment.new(@environment) do
470
483
  node.children = node.children.map {|c| visit(c)}.flatten
471
484
  node
472
485
  end
486
+ ensure
487
+ @in_keyframes = old_in_keyframes
473
488
  end
474
489
 
475
490
  def visit_media(node)
@@ -487,7 +502,7 @@ WARNING
487
502
 
488
503
  def visit_cssimport(node)
489
504
  node.resolved_uri = run_interp([node.uri])
490
- if node.query
505
+ if node.query && !node.query.empty?
491
506
  parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
492
507
  node.filename, node.options[:importer], node.line)
493
508
  node.resolved_query ||= parser.parse_media_query_list
@@ -500,10 +515,7 @@ WARNING
500
515
  def run_interp_no_strip(text)
501
516
  text.map do |r|
502
517
  next r if r.is_a?(String)
503
- val = r.perform(@environment)
504
- # Interpolated strings should never render with quotes
505
- next val.value if val.is_a?(Sass::Script::Value::String)
506
- val.to_s
518
+ r.perform(@environment).to_s(:quote => :none)
507
519
  end.join
508
520
  end
509
521