sass 3.1.0.alpha.48 → 3.1.0.alpha.49

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
1
+ # Visitors are used to traverse the Sass parse tree.
2
+ # Visitors should extend {Visitors::Base},
3
+ # which provides a small amount of scaffolding for traversal.
4
+ module Sass::Tree::Visitors
5
+ # The abstract base class for Sass visitors.
6
+ # Visitors should extend this class,
7
+ # then implement `visit_*` methods for each node they care about
8
+ # (e.g. `visit_rule` for {RuleNode} or `visit_for` for {FileNode}).
9
+ # These methods take the node in question as argument.
10
+ # They may `yield` to visit the child nodes of the current node.
11
+ #
12
+ # *Note*: due to the unusual nature of {Sass::Tree::IfNode},
13
+ # special care must be taken to ensure that it is properly handled.
14
+ # In particular, there is no built-in scaffolding
15
+ # for dealing with the return value of `@else` nodes.
16
+ #
17
+ # @abstract
18
+ class Base
19
+ # Runs the visitor on a tree.
20
+ #
21
+ # @param root [Tree::Node] The root node of the Sass tree.
22
+ # @return [Object] The return value of \{#visit} for the root node.
23
+ def self.visit(root)
24
+ new.send(:visit, root)
25
+ end
26
+
27
+ protected
28
+
29
+ # Runs the visitor on the given node.
30
+ # This can be overridden by subclasses that need to do something for each node.
31
+ #
32
+ # @param node [Tree::Node] The node to visit.
33
+ # @return [Object] The return value of the `visit_*` method for this node.
34
+ def visit(node)
35
+ method = "visit_#{node.class.name.gsub(/.*::(.*?)Node$/, '\\1').downcase}"
36
+ if self.respond_to?(method)
37
+ self.send(method, node) {visit_children(node)}
38
+ else
39
+ visit_children(node)
40
+ end
41
+ end
42
+
43
+ # Visit the child nodes for a given node.
44
+ # This can be overridden by subclasses that need to do something
45
+ # with the child nodes' return values.
46
+ #
47
+ # This method is run when `visit_*` methods `yield`,
48
+ # and its return value is returned from the `yield`.
49
+ #
50
+ # @param parent [Tree::Node] The parent node of the children to visit.
51
+ # @return [Array<Object>] The return values of the `visit_*` methods for the children.
52
+ def visit_children(parent)
53
+ parent.children.map {|c| visit(c)}
54
+ end
55
+
56
+ # `yield`s, then runs the visitor on the `@else` clause if the node has one.
57
+ # This exists to ensure that the contents of the `@else` clause get visited.
58
+ def visit_if(node)
59
+ yield
60
+ visit(node.else) if node.else
61
+ node
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,234 @@
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
+ end
20
+
21
+ def visit_children(parent)
22
+ @tabs += 1
23
+ return @format == :sass ? "\n" : " {}\n" if parent.children.empty?
24
+ (@format == :sass ? "\n" : " {\n") + super.join.rstrip + (@format == :sass ? "\n" : " }\n")
25
+ ensure
26
+ @tabs -= 1
27
+ end
28
+
29
+ # Ensures proper spacing between top-level nodes.
30
+ def visit_root(node)
31
+ Sass::Util.enum_cons(node.children + [nil], 2).map do |child, nxt|
32
+ visit(child) +
33
+ if nxt &&
34
+ (child.is_a?(Sass::Tree::CommentNode) &&
35
+ child.line + child.value.count("\n") + 1 == nxt.line) ||
36
+ (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
37
+ child.line + 1 == nxt.line) ||
38
+ (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
39
+ child.line + 1 == nxt.line)
40
+ ""
41
+ else
42
+ "\n"
43
+ end
44
+ end.join.rstrip + "\n"
45
+ end
46
+
47
+ def visit_charset(node)
48
+ "#{tab_str}@charset \"#{node.name}\"#{semi}\n"
49
+ end
50
+
51
+ def visit_comment(node)
52
+ if @format == :sass
53
+ content = node.value.gsub(/\*\/$/, '').rstrip
54
+ if content =~ /\A[ \t]/
55
+ # Re-indent SCSS comments like this:
56
+ # /* foo
57
+ # bar
58
+ # baz */
59
+ content.gsub!(/^/, ' ')
60
+ content.sub!(/\A([ \t]*)\/\*/, '/*\1')
61
+ end
62
+
63
+ content =
64
+ unless content.include?("\n")
65
+ content
66
+ else
67
+ content.gsub!(/\n( \*|\/\/)/, "\n ")
68
+ spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
69
+ sep = node.silent ? "\n//" : "\n *"
70
+ if spaces >= 2
71
+ content.gsub(/\n /, sep)
72
+ else
73
+ content.gsub(/\n#{' ' * spaces}/, sep)
74
+ end
75
+ end
76
+
77
+ content.gsub!(/\A\/\*/, '//') if node.silent
78
+ content.gsub!(/^/, tab_str)
79
+ content.rstrip + "\n"
80
+ else
81
+ spaces = (' ' * [@tabs - node.value[/^ */].size, 0].max)
82
+ if node.silent
83
+ node.value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
84
+ else
85
+ node.value
86
+ end.gsub(/^/, spaces) + "\n"
87
+ end
88
+ end
89
+
90
+ def visit_debug(node)
91
+ "#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"
92
+ end
93
+
94
+ def visit_directive(node)
95
+ res = "#{tab_str}#{node.value}"
96
+ return res + "#{semi}\n" unless node.has_children
97
+ res + yield + "\n"
98
+ end
99
+
100
+ def visit_each(node)
101
+ "#{tab_str}@each $#{dasherize(node.var)} in #{node.list.to_sass(@options)}#{yield}"
102
+ end
103
+
104
+ def visit_extend(node)
105
+ "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}#{semi}\n"
106
+ end
107
+
108
+ def visit_for(node)
109
+ "#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
110
+ "#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
111
+ end
112
+
113
+ def visit_if(node)
114
+ name =
115
+ if !@is_else; "if"
116
+ elsif node.expr; "else if"
117
+ else; "else"
118
+ end
119
+ str = "#{tab_str}@#{name}"
120
+ str << " #{node.expr.to_sass(@options)}" if node.expr
121
+ str << yield
122
+ @is_else = true
123
+ str << visit(node.else) if node.else
124
+ str
125
+ ensure
126
+ @is_else = false
127
+ end
128
+
129
+ def visit_import(node)
130
+ quote = @format == :scss ? '"' : ''
131
+ "#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n"
132
+ end
133
+
134
+ def visit_media(node)
135
+ "#{tab_str}@media #{node.query}#{yield}"
136
+ end
137
+
138
+ def visit_mixindef(node)
139
+ args =
140
+ if node.args.empty?
141
+ ""
142
+ else
143
+ '(' + node.args.map do |v, d|
144
+ if d
145
+ "#{v.to_sass(@options)}: #{d.to_sass(@options)}"
146
+ else
147
+ v.to_sass(@options)
148
+ end
149
+ end.join(", ") + ')'
150
+ end
151
+
152
+ "#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
153
+ end
154
+
155
+ def visit_mixin(node)
156
+ unless node.args.empty? && node.keywords.empty?
157
+ args = node.args.map {|a| a.to_sass(@options)}.join(", ")
158
+ keywords = node.keywords.map {|k, v| "$#{dasherize(k)}: #{v.to_sass(@options)}"}.join(', ')
159
+ arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
160
+ end
161
+ "#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{semi}\n"
162
+ end
163
+
164
+ def visit_prop(node)
165
+ res = tab_str + node.declaration(@options, @format)
166
+ return res + semi + "\n" if node.children.empty?
167
+ res + yield.rstrip + semi + "\n"
168
+ end
169
+
170
+ def visit_rule(node)
171
+ if @format == :sass
172
+ name = selector_to_sass(node.rule)
173
+ name = "\\" + name if name[0] == ?:
174
+ name.gsub(/^/, tab_str) + yield
175
+ elsif @format == :scss
176
+ name = selector_to_scss(node.rule)
177
+ res = name + yield
178
+ if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.silent
179
+ res.slice!(-3..-1)
180
+ res << "\n" << tab_str << "}\n"
181
+ end
182
+ res
183
+ end
184
+ end
185
+
186
+ def visit_variable(node)
187
+ "#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}#{' !default' if node.guarded}#{semi}\n"
188
+ end
189
+
190
+ def visit_warn(node)
191
+ "#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n"
192
+ end
193
+
194
+ def visit_while(node)
195
+ "#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"
196
+ end
197
+
198
+ private
199
+
200
+ def selector_to_src(sel)
201
+ @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
202
+ end
203
+
204
+ def selector_to_sass(sel)
205
+ sel.map do |r|
206
+ if r.is_a?(String)
207
+ r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "}
208
+ else
209
+ "\#{#{r.to_sass(@options)}}"
210
+ end
211
+ end.join
212
+ end
213
+
214
+ def selector_to_scss(sel)
215
+ sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
216
+ join.gsub(/^[ \t]*/, tab_str)
217
+ end
218
+
219
+ def semi
220
+ @format == :sass ? "" : ";"
221
+ end
222
+
223
+ def tab_str
224
+ ' ' * @tabs
225
+ end
226
+
227
+ def dasherize(s)
228
+ if @options[:dasherize]
229
+ s.gsub('_', '-')
230
+ else
231
+ s
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,177 @@
1
+ # A visitor for converting a static Sass tree into a static CSS tree.
2
+ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
5
+ # *and* the extensions defined for this tree
6
+ def self.visit(root); super; end
7
+
8
+ protected
9
+
10
+ # Returns the immediate parent of the current node.
11
+ # @return [Tree::Node]
12
+ attr_reader :parent
13
+
14
+ def initialize
15
+ @extends = Sass::Util::SubsetMap.new
16
+ end
17
+
18
+ # If an exception is raised, this adds proper metadata to the backtrace.
19
+ def visit(node)
20
+ super(node.dup)
21
+ rescue Sass::SyntaxError => e
22
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
23
+ raise e
24
+ end
25
+
26
+ # Keeps track of the current parent node.
27
+ def visit_children(parent)
28
+ with_parent parent do
29
+ parent.children = super.flatten
30
+ parent
31
+ end
32
+ end
33
+
34
+ # Runs a block of code with the current parent node
35
+ # replaced with the given node.
36
+ #
37
+ # @param parent [Tree::Node] The new parent for the duration of the block.
38
+ # @yield A block in which the parent is set to `parent`.
39
+ # @return [Object] The return value of the block.
40
+ def with_parent(parent)
41
+ old_parent, @parent = @parent, parent
42
+ yield
43
+ ensure
44
+ @parent = old_parent
45
+ end
46
+
47
+ # In Ruby 1.8, ensures that there's only one `@charset` directive
48
+ # and that it's at the top of the document.
49
+ #
50
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
51
+ # *and* the extensions defined for this tree
52
+ def visit_root(node)
53
+ yield
54
+
55
+ # In Ruby 1.9 we can make all @charset nodes invisible
56
+ # and infer the final @charset from the encoding of the final string.
57
+ if Sass::Util.ruby1_8? && parent.nil?
58
+ charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
59
+ node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
60
+ node.children.unshift charset if charset
61
+ end
62
+
63
+ return node, @extends
64
+ rescue Sass::SyntaxError => e
65
+ e.sass_template ||= node.template
66
+ raise e
67
+ end
68
+
69
+ # Registers an extension in the `@extends` subset map.
70
+ def visit_extend(node)
71
+ node.resolved_selector.members.each do |seq|
72
+ if seq.members.size > 1
73
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
74
+ end
75
+
76
+ sseq = seq.members.first
77
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
78
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
79
+ end
80
+
81
+ sel = sseq.members
82
+ parent.resolved_rules.members.each do |seq|
83
+ if !seq.members.last.is_a?(Sass::Selector::SimpleSequence)
84
+ raise Sass::SyntaxError.new("#{seq} can't extend: invalid selector")
85
+ end
86
+
87
+ @extends[sel] = seq
88
+ end
89
+ end
90
+
91
+ []
92
+ end
93
+
94
+ # Modifies exception backtraces to include the imported file.
95
+ def visit_import(node)
96
+ yield
97
+ node.children
98
+ rescue Sass::SyntaxError => e
99
+ e.modify_backtrace(:filename => node.children.first.filename)
100
+ e.add_backtrace(:filename => node.filename, :line => node.line)
101
+ raise e
102
+ end
103
+
104
+ # Bubbles the `@media` directive up through RuleNodes
105
+ # and merges it with other `@media` directives.
106
+ def visit_media(node)
107
+ if parent.is_a?(Sass::Tree::RuleNode)
108
+ new_rule = parent.dup
109
+ new_rule.children = node.children
110
+ node.children = with_parent(node) {Array(visit(new_rule))}
111
+ # If the last child is actually the end of the group,
112
+ # the parent's cssize will set it properly
113
+ node.children.last.group_end = false unless node.children.empty?
114
+ else
115
+ yield
116
+ end
117
+
118
+ media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
119
+ node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
120
+ media.each {|n| n.query = "#{node.query} and #{n.query}"}
121
+ (node.children.empty? ? [] : [node]) + media
122
+ end
123
+
124
+ # Asserts that all the mixin's children are valid in their new location.
125
+ def visit_mixin(node)
126
+ node.children.map do |c|
127
+ parent.check_child! c
128
+ # Don't use #visit_children to avoid adding the mixin node to the list of parents.
129
+ visit(c)
130
+ end.flatten
131
+ rescue Sass::SyntaxError => e
132
+ e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
133
+ e.add_backtrace(:filename => node.filename, :line => node.line)
134
+ raise e
135
+ end
136
+
137
+ # Converts nested properties into flat properties
138
+ # and updates the indentation of the prop node based on the nesting level.
139
+ def visit_prop(node)
140
+ if parent.is_a?(Sass::Tree::PropNode)
141
+ node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
142
+ node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
143
+ end
144
+
145
+ yield
146
+
147
+ if !node.resolved_value.empty? || node.children.empty?
148
+ node.send(:check!)
149
+ node.children.unshift(node)
150
+ end
151
+
152
+ node.children
153
+ end
154
+
155
+ # Resolves parent references and nested selectors,
156
+ # and updates the indentation of the rule node based on the nesting level.
157
+ def visit_rule(node)
158
+ parent_resolved_rules = parent.is_a?(Sass::Tree::RuleNode) ? parent.resolved_rules : nil
159
+ # It's possible for resolved_rules to be set if we've duplicated this node during @media bubbling
160
+ node.resolved_rules ||= node.parsed_rules.resolve_parent_refs(parent_resolved_rules)
161
+
162
+ yield
163
+
164
+ rules = node.children.select {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode)}
165
+ props = node.children.reject {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode) || c.invisible?}
166
+
167
+ unless props.empty?
168
+ node.children = props
169
+ rules.each {|r| r.tabs += 1} if node.style == :nested
170
+ rules.unshift(node)
171
+ end
172
+
173
+ rules.last.group_end = true unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty?
174
+
175
+ rules
176
+ end
177
+ end