sass4 4.0.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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/AGENTS.md +534 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/CONTRIBUTING.md +148 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +242 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/sass-spec-ref.sh +40 -0
  14. data/extra/update_watch.rb +13 -0
  15. data/init.rb +18 -0
  16. data/lib/sass/cache_stores/base.rb +88 -0
  17. data/lib/sass/cache_stores/chain.rb +34 -0
  18. data/lib/sass/cache_stores/filesystem.rb +60 -0
  19. data/lib/sass/cache_stores/memory.rb +46 -0
  20. data/lib/sass/cache_stores/null.rb +25 -0
  21. data/lib/sass/cache_stores.rb +15 -0
  22. data/lib/sass/callbacks.rb +67 -0
  23. data/lib/sass/css.rb +407 -0
  24. data/lib/sass/deprecation.rb +55 -0
  25. data/lib/sass/engine.rb +1236 -0
  26. data/lib/sass/environment.rb +236 -0
  27. data/lib/sass/error.rb +198 -0
  28. data/lib/sass/exec/base.rb +188 -0
  29. data/lib/sass/exec/sass_convert.rb +283 -0
  30. data/lib/sass/exec/sass_scss.rb +436 -0
  31. data/lib/sass/exec.rb +9 -0
  32. data/lib/sass/features.rb +48 -0
  33. data/lib/sass/importers/base.rb +182 -0
  34. data/lib/sass/importers/deprecated_path.rb +51 -0
  35. data/lib/sass/importers/filesystem.rb +221 -0
  36. data/lib/sass/importers.rb +23 -0
  37. data/lib/sass/logger/base.rb +47 -0
  38. data/lib/sass/logger/delayed.rb +50 -0
  39. data/lib/sass/logger/log_level.rb +45 -0
  40. data/lib/sass/logger.rb +17 -0
  41. data/lib/sass/media.rb +210 -0
  42. data/lib/sass/plugin/compiler.rb +552 -0
  43. data/lib/sass/plugin/configuration.rb +134 -0
  44. data/lib/sass/plugin/generic.rb +15 -0
  45. data/lib/sass/plugin/merb.rb +48 -0
  46. data/lib/sass/plugin/rack.rb +60 -0
  47. data/lib/sass/plugin/rails.rb +47 -0
  48. data/lib/sass/plugin/staleness_checker.rb +199 -0
  49. data/lib/sass/plugin.rb +134 -0
  50. data/lib/sass/railtie.rb +10 -0
  51. data/lib/sass/repl.rb +57 -0
  52. data/lib/sass/root.rb +7 -0
  53. data/lib/sass/script/css_lexer.rb +33 -0
  54. data/lib/sass/script/css_parser.rb +36 -0
  55. data/lib/sass/script/functions.rb +3103 -0
  56. data/lib/sass/script/lexer.rb +518 -0
  57. data/lib/sass/script/parser.rb +1164 -0
  58. data/lib/sass/script/tree/funcall.rb +314 -0
  59. data/lib/sass/script/tree/interpolation.rb +220 -0
  60. data/lib/sass/script/tree/list_literal.rb +119 -0
  61. data/lib/sass/script/tree/literal.rb +49 -0
  62. data/lib/sass/script/tree/map_literal.rb +64 -0
  63. data/lib/sass/script/tree/node.rb +119 -0
  64. data/lib/sass/script/tree/operation.rb +149 -0
  65. data/lib/sass/script/tree/selector.rb +26 -0
  66. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  67. data/lib/sass/script/tree/unary_operation.rb +69 -0
  68. data/lib/sass/script/tree/variable.rb +57 -0
  69. data/lib/sass/script/tree.rb +16 -0
  70. data/lib/sass/script/value/arg_list.rb +36 -0
  71. data/lib/sass/script/value/base.rb +258 -0
  72. data/lib/sass/script/value/bool.rb +35 -0
  73. data/lib/sass/script/value/callable.rb +25 -0
  74. data/lib/sass/script/value/color.rb +704 -0
  75. data/lib/sass/script/value/function.rb +19 -0
  76. data/lib/sass/script/value/helpers.rb +298 -0
  77. data/lib/sass/script/value/list.rb +135 -0
  78. data/lib/sass/script/value/map.rb +70 -0
  79. data/lib/sass/script/value/null.rb +44 -0
  80. data/lib/sass/script/value/number.rb +564 -0
  81. data/lib/sass/script/value/string.rb +138 -0
  82. data/lib/sass/script/value.rb +13 -0
  83. data/lib/sass/script.rb +66 -0
  84. data/lib/sass/scss/css_parser.rb +61 -0
  85. data/lib/sass/scss/parser.rb +1343 -0
  86. data/lib/sass/scss/rx.rb +134 -0
  87. data/lib/sass/scss/static_parser.rb +351 -0
  88. data/lib/sass/scss.rb +14 -0
  89. data/lib/sass/selector/abstract_sequence.rb +112 -0
  90. data/lib/sass/selector/comma_sequence.rb +195 -0
  91. data/lib/sass/selector/pseudo.rb +291 -0
  92. data/lib/sass/selector/sequence.rb +661 -0
  93. data/lib/sass/selector/simple.rb +124 -0
  94. data/lib/sass/selector/simple_sequence.rb +348 -0
  95. data/lib/sass/selector.rb +327 -0
  96. data/lib/sass/shared.rb +76 -0
  97. data/lib/sass/source/map.rb +209 -0
  98. data/lib/sass/source/position.rb +39 -0
  99. data/lib/sass/source/range.rb +41 -0
  100. data/lib/sass/stack.rb +140 -0
  101. data/lib/sass/supports.rb +225 -0
  102. data/lib/sass/tree/at_root_node.rb +83 -0
  103. data/lib/sass/tree/charset_node.rb +22 -0
  104. data/lib/sass/tree/comment_node.rb +82 -0
  105. data/lib/sass/tree/content_node.rb +9 -0
  106. data/lib/sass/tree/css_import_node.rb +68 -0
  107. data/lib/sass/tree/debug_node.rb +18 -0
  108. data/lib/sass/tree/directive_node.rb +59 -0
  109. data/lib/sass/tree/each_node.rb +24 -0
  110. data/lib/sass/tree/error_node.rb +18 -0
  111. data/lib/sass/tree/extend_node.rb +43 -0
  112. data/lib/sass/tree/for_node.rb +36 -0
  113. data/lib/sass/tree/function_node.rb +44 -0
  114. data/lib/sass/tree/if_node.rb +52 -0
  115. data/lib/sass/tree/import_node.rb +75 -0
  116. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  117. data/lib/sass/tree/media_node.rb +48 -0
  118. data/lib/sass/tree/mixin_def_node.rb +38 -0
  119. data/lib/sass/tree/mixin_node.rb +52 -0
  120. data/lib/sass/tree/node.rb +240 -0
  121. data/lib/sass/tree/prop_node.rb +162 -0
  122. data/lib/sass/tree/return_node.rb +19 -0
  123. data/lib/sass/tree/root_node.rb +44 -0
  124. data/lib/sass/tree/rule_node.rb +153 -0
  125. data/lib/sass/tree/supports_node.rb +38 -0
  126. data/lib/sass/tree/trace_node.rb +33 -0
  127. data/lib/sass/tree/variable_node.rb +36 -0
  128. data/lib/sass/tree/visitors/base.rb +72 -0
  129. data/lib/sass/tree/visitors/check_nesting.rb +173 -0
  130. data/lib/sass/tree/visitors/convert.rb +350 -0
  131. data/lib/sass/tree/visitors/cssize.rb +362 -0
  132. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  133. data/lib/sass/tree/visitors/extend.rb +64 -0
  134. data/lib/sass/tree/visitors/perform.rb +572 -0
  135. data/lib/sass/tree/visitors/set_options.rb +139 -0
  136. data/lib/sass/tree/visitors/to_css.rb +440 -0
  137. data/lib/sass/tree/warn_node.rb +18 -0
  138. data/lib/sass/tree/while_node.rb +18 -0
  139. data/lib/sass/util/multibyte_string_scanner.rb +151 -0
  140. data/lib/sass/util/normalized_map.rb +122 -0
  141. data/lib/sass/util/subset_map.rb +109 -0
  142. data/lib/sass/util/test.rb +9 -0
  143. data/lib/sass/util.rb +1137 -0
  144. data/lib/sass/version.rb +120 -0
  145. data/lib/sass.rb +102 -0
  146. data/rails/init.rb +1 -0
  147. metadata +283 -0
@@ -0,0 +1,173 @@
1
+ # A visitor for checking that all nodes are properly nested.
2
+ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
3
+ protected
4
+
5
+ def initialize
6
+ @parents = []
7
+ @parent = nil
8
+ @current_mixin_def = nil
9
+ end
10
+
11
+ def visit(node)
12
+ if (error = @parent && (
13
+ try_send(@parent.class.invalid_child_method_name, @parent, node) ||
14
+ try_send(node.class.invalid_parent_method_name, @parent, node)))
15
+ raise Sass::SyntaxError.new(error)
16
+ end
17
+ super
18
+ rescue Sass::SyntaxError => e
19
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
20
+ raise e
21
+ end
22
+
23
+ CONTROL_NODES = [Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
24
+ Sass::Tree::WhileNode, Sass::Tree::TraceNode]
25
+ SCRIPT_NODES = [Sass::Tree::ImportNode] + CONTROL_NODES
26
+ def visit_children(parent)
27
+ old_parent = @parent
28
+
29
+ # When checking a static tree, resolve at-roots to be sure they won't send
30
+ # nodes where they don't belong.
31
+ if parent.is_a?(Sass::Tree::AtRootNode) && parent.resolved_value
32
+ old_parents = @parents
33
+ @parents = @parents.reject {|p| parent.exclude_node?(p)}
34
+ @parent = @parents.reverse.each_with_index.
35
+ find {|p, i| !transparent_parent?(p, @parents[-i - 2])}.first
36
+
37
+ begin
38
+ return super
39
+ ensure
40
+ @parents = old_parents
41
+ @parent = old_parent
42
+ end
43
+ end
44
+
45
+ unless transparent_parent?(parent, old_parent)
46
+ @parent = parent
47
+ end
48
+
49
+ @parents.push parent
50
+ begin
51
+ super
52
+ ensure
53
+ @parent = old_parent
54
+ @parents.pop
55
+ end
56
+ end
57
+
58
+ def visit_root(node)
59
+ yield
60
+ rescue Sass::SyntaxError => e
61
+ e.sass_template ||= node.template
62
+ raise e
63
+ end
64
+
65
+ def visit_import(node)
66
+ yield
67
+ rescue Sass::SyntaxError => e
68
+ e.modify_backtrace(:filename => node.children.first.filename)
69
+ e.add_backtrace(:filename => node.filename, :line => node.line)
70
+ raise e
71
+ end
72
+
73
+ def visit_mixindef(node)
74
+ @current_mixin_def, old_mixin_def = node, @current_mixin_def
75
+ yield
76
+ ensure
77
+ @current_mixin_def = old_mixin_def
78
+ end
79
+
80
+ def invalid_content_parent?(parent, child)
81
+ if @current_mixin_def
82
+ @current_mixin_def.has_content = true
83
+ nil
84
+ else
85
+ "@content may only be used within a mixin."
86
+ end
87
+ end
88
+
89
+ def invalid_charset_parent?(parent, child)
90
+ "@charset may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
91
+ end
92
+
93
+ VALID_EXTEND_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
94
+ def invalid_extend_parent?(parent, child)
95
+ return if is_any_of?(parent, VALID_EXTEND_PARENTS)
96
+ "Extend directives may only be used within rules."
97
+ end
98
+
99
+ INVALID_IMPORT_PARENTS = CONTROL_NODES +
100
+ [Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
101
+ def invalid_import_parent?(parent, child)
102
+ unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
103
+ return "Import directives may not be used within control directives or mixins."
104
+ end
105
+ return if parent.is_a?(Sass::Tree::RootNode)
106
+ return "CSS import directives may only be used at the root of a document." if child.css_import?
107
+ rescue Sass::SyntaxError => e
108
+ e.modify_backtrace(:filename => child.imported_file.options[:filename])
109
+ e.add_backtrace(:filename => child.filename, :line => child.line)
110
+ raise e
111
+ end
112
+
113
+ def invalid_mixindef_parent?(parent, child)
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
+ end
117
+
118
+ def invalid_function_parent?(parent, child)
119
+ return if (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
120
+ "Functions may not be defined within control directives or other mixins."
121
+ end
122
+
123
+ VALID_FUNCTION_CHILDREN = [
124
+ Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
125
+ Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::ErrorNode
126
+ ] + CONTROL_NODES
127
+ def invalid_function_child?(parent, child)
128
+ return if is_any_of?(child, VALID_FUNCTION_CHILDREN)
129
+ "Functions can only contain variable declarations and control directives."
130
+ end
131
+
132
+ VALID_PROP_CHILDREN = CONTROL_NODES + [Sass::Tree::CommentNode,
133
+ Sass::Tree::PropNode,
134
+ Sass::Tree::MixinNode]
135
+ def invalid_prop_child?(parent, child)
136
+ return if is_any_of?(child, VALID_PROP_CHILDREN)
137
+ "Illegal nesting: Only properties may be nested beneath properties."
138
+ end
139
+
140
+ VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::KeyframeRuleNode, Sass::Tree::PropNode,
141
+ Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode, Sass::Tree::MixinNode]
142
+ def invalid_prop_parent?(parent, child)
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
146
+ end
147
+
148
+ def invalid_return_parent?(parent, child)
149
+ "@return may only be used within a function." unless parent.is_a?(Sass::Tree::FunctionNode)
150
+ end
151
+
152
+ private
153
+
154
+ # Whether `parent` should be assigned to `@parent`.
155
+ def transparent_parent?(parent, grandparent)
156
+ is_any_of?(parent, SCRIPT_NODES) ||
157
+ (parent.bubbles? &&
158
+ !grandparent.is_a?(Sass::Tree::RootNode) &&
159
+ !grandparent.is_a?(Sass::Tree::AtRootNode))
160
+ end
161
+
162
+ def is_any_of?(val, classes)
163
+ classes.each do |c|
164
+ return true if val.is_a?(c)
165
+ end
166
+ false
167
+ end
168
+
169
+ def try_send(method, *args)
170
+ return unless respond_to?(method, true)
171
+ send(method, *args)
172
+ end
173
+ end
@@ -0,0 +1,350 @@
1
+ # A visitor for converting a Sass tree into a source string.
2
+ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
3
+ # Runs the visitor on a tree.
4
+ #
5
+ # @param root [Tree::Node] The root node of the Sass tree.
6
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
7
+ # @param format [Symbol] `:sass` or `:scss`.
8
+ # @return [String] The Sass or SCSS source for the tree.
9
+ def self.visit(root, options, format)
10
+ new(options, format).send(:visit, root)
11
+ end
12
+
13
+ protected
14
+
15
+ def initialize(options, format)
16
+ @options = options
17
+ @format = format
18
+ @tabs = 0
19
+ # 2 spaces by default
20
+ @tab_chars = @options[:indent] || " "
21
+ @is_else = false
22
+ end
23
+
24
+ def visit_children(parent)
25
+ @tabs += 1
26
+ return @format == :sass ? "\n" : " {}\n" if parent.children.empty?
27
+
28
+ res = visit_rule_level(parent.children)
29
+
30
+ if @format == :sass
31
+ "\n" + res.rstrip + "\n"
32
+ else
33
+ " {\n" + res.rstrip + "\n#{@tab_chars * (@tabs - 1)}}\n"
34
+ end
35
+ ensure
36
+ @tabs -= 1
37
+ end
38
+
39
+ # Ensures proper spacing between top-level nodes.
40
+ def visit_root(node)
41
+ visit_rule_level(node.children)
42
+ end
43
+
44
+ def visit_charset(node)
45
+ "#{tab_str}@charset \"#{node.name}\"#{semi}\n"
46
+ end
47
+
48
+ def visit_comment(node)
49
+ value = interp_to_src(node.value)
50
+ if @format == :sass
51
+ content = value.gsub(%r{\*/$}, '').rstrip
52
+ if content =~ /\A[ \t]/
53
+ # Re-indent SCSS comments like this:
54
+ # /* foo
55
+ # bar
56
+ # baz */
57
+ content.gsub!(/^/, ' ')
58
+ content.sub!(%r{\A([ \t]*)/\*}, '/*\1')
59
+ end
60
+
61
+ if content.include?("\n")
62
+ content.gsub!(/\n \*/, "\n ")
63
+ spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
64
+ sep = node.type == :silent ? "\n//" : "\n *"
65
+ if spaces >= 2
66
+ content.gsub!(/\n /, sep)
67
+ else
68
+ content.gsub!(/\n#{' ' * spaces}/, sep)
69
+ end
70
+ end
71
+
72
+ content.gsub!(%r{\A/\*}, '//') if node.type == :silent
73
+ content.gsub!(/^/, tab_str)
74
+ content = content.rstrip + "\n"
75
+ else
76
+ spaces = (@tab_chars * [@tabs - value[/^ */].size, 0].max)
77
+ content = if node.type == :silent
78
+ value.gsub(%r{^[/ ]\*}, '//').gsub(%r{ *\*/$}, '')
79
+ else
80
+ value
81
+ end.gsub(/^/, spaces) + "\n"
82
+ end
83
+ content
84
+ end
85
+
86
+ def visit_debug(node)
87
+ "#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"
88
+ end
89
+
90
+ def visit_error(node)
91
+ "#{tab_str}@error #{node.expr.to_sass(@options)}#{semi}\n"
92
+ end
93
+
94
+ def visit_directive(node)
95
+ res = "#{tab_str}#{interp_to_src(node.value)}"
96
+ res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2')
97
+ return res + "#{semi}\n" unless node.has_children
98
+ res + yield
99
+ end
100
+
101
+ def visit_each(node)
102
+ vars = node.vars.map {|var| "$#{dasherize(var)}"}.join(", ")
103
+ "#{tab_str}@each #{vars} in #{node.list.to_sass(@options)}#{yield}"
104
+ end
105
+
106
+ def visit_extend(node)
107
+ "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}" +
108
+ "#{' !optional' if node.optional?}#{semi}\n"
109
+ end
110
+
111
+ def visit_for(node)
112
+ "#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
113
+ "#{node.exclusive ? 'to' : 'through'} #{node.to.to_sass(@options)}#{yield}"
114
+ end
115
+
116
+ def visit_function(node)
117
+ args = node.args.map do |v, d|
118
+ d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
119
+ end.join(", ")
120
+ if node.splat
121
+ args << ", " unless node.args.empty?
122
+ args << node.splat.to_sass(@options) << "..."
123
+ end
124
+
125
+ "#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"
126
+ end
127
+
128
+ def visit_if(node)
129
+ name =
130
+ if !@is_else
131
+ "if"
132
+ elsif node.expr
133
+ "else if"
134
+ else
135
+ "else"
136
+ end
137
+ @is_else = false
138
+ str = "#{tab_str}@#{name}"
139
+ str << " #{node.expr.to_sass(@options)}" if node.expr
140
+ str << yield
141
+ @is_else = true
142
+ str << visit(node.else) if node.else
143
+ str
144
+ ensure
145
+ @is_else = false
146
+ end
147
+
148
+ def visit_import(node)
149
+ quote = @format == :scss ? '"' : ''
150
+ "#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n"
151
+ end
152
+
153
+ def visit_media(node)
154
+ "#{tab_str}@media #{query_interp_to_src(node.query)}#{yield}"
155
+ end
156
+
157
+ def visit_supports(node)
158
+ "#{tab_str}@#{node.name} #{node.condition.to_src(@options)}#{yield}"
159
+ end
160
+
161
+ def visit_cssimport(node)
162
+ if node.uri.is_a?(Sass::Script::Tree::Node)
163
+ str = "#{tab_str}@import #{node.uri.to_sass(@options)}"
164
+ else
165
+ str = "#{tab_str}@import #{node.uri}"
166
+ end
167
+ str << " supports(#{node.supports_condition.to_src(@options)})" if node.supports_condition
168
+ str << " #{interp_to_src(node.query)}" unless node.query.empty?
169
+ "#{str}#{semi}\n"
170
+ end
171
+
172
+ def visit_mixindef(node)
173
+ args =
174
+ if node.args.empty? && node.splat.nil?
175
+ ""
176
+ else
177
+ str = '('
178
+ str << node.args.map do |v, d|
179
+ if d
180
+ "#{v.to_sass(@options)}: #{d.to_sass(@options)}"
181
+ else
182
+ v.to_sass(@options)
183
+ end
184
+ end.join(", ")
185
+
186
+ if node.splat
187
+ str << ", " unless node.args.empty?
188
+ str << node.splat.to_sass(@options) << '...'
189
+ end
190
+
191
+ str << ')'
192
+ end
193
+
194
+ "#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
195
+ end
196
+
197
+ def visit_mixin(node)
198
+ arg_to_sass = lambda do |arg|
199
+ sass = arg.to_sass(@options)
200
+ sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma
201
+ sass
202
+ end
203
+
204
+ unless node.args.empty? && node.keywords.empty? && node.splat.nil?
205
+ args = node.args.map(&arg_to_sass)
206
+ keywords = node.keywords.as_stored.to_a.map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"}
207
+
208
+ if node.splat
209
+ splat = "#{arg_to_sass[node.splat]}..."
210
+ kwarg_splat = "#{arg_to_sass[node.kwarg_splat]}..." if node.kwarg_splat
211
+ end
212
+
213
+ arglist = "(#{[args, splat, keywords, kwarg_splat].flatten.compact.join(', ')})"
214
+ end
215
+ "#{tab_str}#{@format == :sass ? '+' : '@include '}" +
216
+ "#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n"
217
+ end
218
+
219
+ def visit_content(node)
220
+ "#{tab_str}@content#{semi}\n"
221
+ end
222
+
223
+ def visit_prop(node)
224
+ res = tab_str + node.declaration(@options, @format)
225
+ return res + semi + "\n" if node.children.empty?
226
+ res + yield.rstrip + semi + "\n"
227
+ end
228
+
229
+ def visit_return(node)
230
+ "#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n"
231
+ end
232
+
233
+ def visit_rule(node)
234
+ rule = node.parsed_rules ? [node.parsed_rules.to_s] : node.rule
235
+ if @format == :sass
236
+ name = selector_to_sass(rule)
237
+ name = "\\" + name if name[0] == ?:
238
+ name.gsub(/^/, tab_str) + yield
239
+ elsif @format == :scss
240
+ name = selector_to_scss(rule)
241
+ res = name + yield
242
+ if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent
243
+ res.slice!(-3..-1)
244
+ res << "\n" << tab_str << "}\n"
245
+ end
246
+ res
247
+ end
248
+ end
249
+
250
+ def visit_variable(node)
251
+ "#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}" +
252
+ "#{' !global' if node.global}#{' !default' if node.guarded}#{semi}\n"
253
+ end
254
+
255
+ def visit_warn(node)
256
+ "#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n"
257
+ end
258
+
259
+ def visit_while(node)
260
+ "#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"
261
+ end
262
+
263
+ def visit_atroot(node)
264
+ if node.query
265
+ "#{tab_str}@at-root #{query_interp_to_src(node.query)}#{yield}"
266
+ elsif node.children.length == 1 && node.children.first.is_a?(Sass::Tree::RuleNode)
267
+ rule = node.children.first
268
+ "#{tab_str}@at-root #{selector_to_src(rule.rule).lstrip}#{visit_children(rule)}"
269
+ else
270
+ "#{tab_str}@at-root#{yield}"
271
+ end
272
+ end
273
+
274
+ def visit_keyframerule(node)
275
+ "#{tab_str}#{node.resolved_value}#{yield}"
276
+ end
277
+
278
+ private
279
+
280
+ # Visit rule-level nodes and return their conversion with appropriate
281
+ # whitespace added.
282
+ def visit_rule_level(nodes)
283
+ (nodes + [nil]).each_cons(2).map do |child, nxt|
284
+ visit(child) +
285
+ if nxt &&
286
+ (child.is_a?(Sass::Tree::CommentNode) && child.line + child.lines + 1 == nxt.line) ||
287
+ (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
288
+ child.line + 1 == nxt.line) ||
289
+ (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
290
+ child.line + 1 == nxt.line) ||
291
+ (child.is_a?(Sass::Tree::PropNode) && nxt.is_a?(Sass::Tree::PropNode)) ||
292
+ (child.is_a?(Sass::Tree::MixinNode) && nxt.is_a?(Sass::Tree::MixinNode) &&
293
+ child.line + 1 == nxt.line)
294
+ ""
295
+ else
296
+ "\n"
297
+ end
298
+ end.join.rstrip + "\n"
299
+ end
300
+
301
+ def interp_to_src(interp)
302
+ interp.map {|r| r.is_a?(String) ? r : r.to_sass(@options)}.join
303
+ end
304
+
305
+ # Like interp_to_src, but removes the unnecessary `#{}` around the keys and
306
+ # values in query expressions.
307
+ def query_interp_to_src(interp)
308
+ interp = interp.map do |e|
309
+ next e unless e.is_a?(Sass::Script::Tree::Literal)
310
+ next e unless e.value.is_a?(Sass::Script::Value::String)
311
+ e.value.value
312
+ end
313
+
314
+ interp_to_src(interp)
315
+ end
316
+
317
+ def selector_to_src(sel)
318
+ @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
319
+ end
320
+
321
+ def selector_to_sass(sel)
322
+ sel.map do |r|
323
+ if r.is_a?(String)
324
+ r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
325
+ else
326
+ r.to_sass(@options)
327
+ end
328
+ end.join
329
+ end
330
+
331
+ def selector_to_scss(sel)
332
+ interp_to_src(sel).gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
333
+ end
334
+
335
+ def semi
336
+ @format == :sass ? "" : ";"
337
+ end
338
+
339
+ def tab_str
340
+ @tab_chars * @tabs
341
+ end
342
+
343
+ def dasherize(s)
344
+ if @options[:dasherize]
345
+ s.tr('_', '-')
346
+ else
347
+ s
348
+ end
349
+ end
350
+ end