sass 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +58 -50
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/bin/sass +1 -1
  9. data/bin/scss +1 -1
  10. data/lib/sass/cache_stores/filesystem.rb +6 -2
  11. data/lib/sass/css.rb +1 -3
  12. data/lib/sass/engine.rb +37 -46
  13. data/lib/sass/environment.rb +13 -17
  14. data/lib/sass/error.rb +6 -9
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +424 -0
  18. data/lib/sass/exec.rb +5 -771
  19. data/lib/sass/features.rb +7 -0
  20. data/lib/sass/importers/base.rb +7 -2
  21. data/lib/sass/importers/filesystem.rb +9 -25
  22. data/lib/sass/importers.rb +0 -1
  23. data/lib/sass/media.rb +1 -4
  24. data/lib/sass/plugin/compiler.rb +200 -83
  25. data/lib/sass/plugin/staleness_checker.rb +1 -1
  26. data/lib/sass/plugin.rb +3 -3
  27. data/lib/sass/script/css_lexer.rb +1 -1
  28. data/lib/sass/script/functions.rb +622 -268
  29. data/lib/sass/script/lexer.rb +99 -34
  30. data/lib/sass/script/parser.rb +24 -23
  31. data/lib/sass/script/tree/funcall.rb +1 -1
  32. data/lib/sass/script/tree/interpolation.rb +20 -2
  33. data/lib/sass/script/tree/selector.rb +26 -0
  34. data/lib/sass/script/tree/string_interpolation.rb +1 -1
  35. data/lib/sass/script/tree.rb +1 -0
  36. data/lib/sass/script/value/base.rb +7 -5
  37. data/lib/sass/script/value/bool.rb +0 -5
  38. data/lib/sass/script/value/color.rb +39 -21
  39. data/lib/sass/script/value/helpers.rb +107 -0
  40. data/lib/sass/script/value/list.rb +0 -15
  41. data/lib/sass/script/value/null.rb +0 -5
  42. data/lib/sass/script/value/number.rb +62 -14
  43. data/lib/sass/script/value/string.rb +59 -11
  44. data/lib/sass/script/value.rb +0 -1
  45. data/lib/sass/scss/css_parser.rb +8 -2
  46. data/lib/sass/scss/parser.rb +190 -328
  47. data/lib/sass/scss/rx.rb +15 -6
  48. data/lib/sass/scss/static_parser.rb +298 -1
  49. data/lib/sass/selector/abstract_sequence.rb +28 -13
  50. data/lib/sass/selector/comma_sequence.rb +92 -13
  51. data/lib/sass/selector/pseudo.rb +256 -0
  52. data/lib/sass/selector/sequence.rb +94 -24
  53. data/lib/sass/selector/simple.rb +14 -25
  54. data/lib/sass/selector/simple_sequence.rb +97 -33
  55. data/lib/sass/selector.rb +57 -194
  56. data/lib/sass/shared.rb +1 -1
  57. data/lib/sass/source/map.rb +26 -12
  58. data/lib/sass/stack.rb +0 -6
  59. data/lib/sass/supports.rb +2 -3
  60. data/lib/sass/tree/at_root_node.rb +1 -0
  61. data/lib/sass/tree/charset_node.rb +1 -1
  62. data/lib/sass/tree/directive_node.rb +8 -2
  63. data/lib/sass/tree/error_node.rb +18 -0
  64. data/lib/sass/tree/extend_node.rb +1 -1
  65. data/lib/sass/tree/function_node.rb +4 -0
  66. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  67. data/lib/sass/tree/prop_node.rb +1 -1
  68. data/lib/sass/tree/rule_node.rb +12 -7
  69. data/lib/sass/tree/visitors/check_nesting.rb +38 -10
  70. data/lib/sass/tree/visitors/convert.rb +16 -18
  71. data/lib/sass/tree/visitors/cssize.rb +29 -29
  72. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  73. data/lib/sass/tree/visitors/perform.rb +45 -33
  74. data/lib/sass/tree/visitors/set_options.rb +14 -0
  75. data/lib/sass/tree/visitors/to_css.rb +15 -14
  76. data/lib/sass/util/subset_map.rb +1 -1
  77. data/lib/sass/util.rb +222 -99
  78. data/lib/sass/version.rb +5 -5
  79. data/lib/sass.rb +0 -5
  80. data/test/sass/cache_test.rb +62 -20
  81. data/test/sass/callbacks_test.rb +1 -1
  82. data/test/sass/compiler_test.rb +19 -10
  83. data/test/sass/conversion_test.rb +58 -1
  84. data/test/sass/css2sass_test.rb +23 -4
  85. data/test/sass/encoding_test.rb +219 -0
  86. data/test/sass/engine_test.rb +136 -199
  87. data/test/sass/exec_test.rb +2 -2
  88. data/test/sass/extend_test.rb +236 -19
  89. data/test/sass/functions_test.rb +295 -253
  90. data/test/sass/importer_test.rb +31 -21
  91. data/test/sass/logger_test.rb +1 -1
  92. data/test/sass/more_results/more_import.css +1 -1
  93. data/test/sass/plugin_test.rb +14 -13
  94. data/test/sass/results/compact.css +1 -1
  95. data/test/sass/results/complex.css +4 -4
  96. data/test/sass/results/expanded.css +1 -1
  97. data/test/sass/results/import.css +1 -1
  98. data/test/sass/results/import_charset_ibm866.css +2 -2
  99. data/test/sass/results/mixins.css +17 -17
  100. data/test/sass/results/nested.css +1 -1
  101. data/test/sass/results/parent_ref.css +2 -2
  102. data/test/sass/results/script.css +3 -3
  103. data/test/sass/results/scss_import.css +1 -1
  104. data/test/sass/script_conversion_test.rb +10 -7
  105. data/test/sass/script_test.rb +288 -74
  106. data/test/sass/scss/css_test.rb +141 -24
  107. data/test/sass/scss/rx_test.rb +4 -4
  108. data/test/sass/scss/scss_test.rb +457 -18
  109. data/test/sass/source_map_test.rb +115 -25
  110. data/test/sass/superselector_test.rb +191 -0
  111. data/test/sass/templates/scss_import.scss +2 -1
  112. data/test/sass/test_helper.rb +1 -1
  113. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  114. data/test/sass/util/normalized_map_test.rb +1 -1
  115. data/test/sass/util/subset_map_test.rb +2 -2
  116. data/test/sass/util_test.rb +31 -1
  117. data/test/sass/value_helpers_test.rb +5 -7
  118. data/test/test_helper.rb +2 -2
  119. data/vendor/listen/CHANGELOG.md +1 -228
  120. data/vendor/listen/Gemfile +5 -15
  121. data/vendor/listen/README.md +111 -77
  122. data/vendor/listen/Rakefile +0 -42
  123. data/vendor/listen/lib/listen/adapter.rb +195 -82
  124. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  125. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  126. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  127. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  128. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  129. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  130. data/vendor/listen/lib/listen/listener.rb +135 -37
  131. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  132. data/vendor/listen/lib/listen/version.rb +1 -1
  133. data/vendor/listen/lib/listen.rb +33 -19
  134. data/vendor/listen/listen.gemspec +6 -0
  135. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  136. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  137. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  138. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  139. data/vendor/listen/spec/listen_spec.rb +15 -21
  140. data/vendor/listen/spec/spec_helper.rb +4 -0
  141. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  142. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  143. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  144. metadata +25 -22
  145. data/ext/mkrf_conf.rb +0 -27
  146. data/lib/sass/importers/deprecated_path.rb +0 -51
  147. data/lib/sass/script/value/deprecated_false.rb +0 -55
  148. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  149. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  150. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  151. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -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