sass 3.1.0.alpha.50 → 3.1.0.alpha.51

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1 @@
1
- 3.1.0.alpha.50
1
+ 3.1.0.alpha.51
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.0.alpha.50
1
+ 3.1.0.alpha.51
@@ -67,9 +67,6 @@ end
67
67
 
68
68
  require 'sass/util'
69
69
 
70
- dir = Sass::Util.scope("vendor/fssm/lib")
71
- $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
72
-
73
70
  require 'sass/engine'
74
71
  require 'sass/plugin' if defined?(Merb::Plugins)
75
72
  require 'sass/railtie'
@@ -12,6 +12,8 @@ require 'sass/tree/media_node'
12
12
  require 'sass/tree/variable_node'
13
13
  require 'sass/tree/mixin_def_node'
14
14
  require 'sass/tree/mixin_node'
15
+ require 'sass/tree/function_node'
16
+ require 'sass/tree/return_node'
15
17
  require 'sass/tree/extend_node'
16
18
  require 'sass/tree/if_node'
17
19
  require 'sass/tree/while_node'
@@ -26,6 +28,7 @@ require 'sass/tree/visitors/perform'
26
28
  require 'sass/tree/visitors/cssize'
27
29
  require 'sass/tree/visitors/convert'
28
30
  require 'sass/tree/visitors/to_css'
31
+ require 'sass/tree/visitors/check_nesting'
29
32
  require 'sass/selector'
30
33
  require 'sass/environment'
31
34
  require 'sass/script'
@@ -36,24 +39,24 @@ require 'sass/shared'
36
39
 
37
40
  module Sass
38
41
 
39
- # A Sass mixin.
42
+ # A Sass mixin or function.
40
43
  #
41
44
  # `name`: `String`
42
- # : The name of the mixin.
45
+ # : The name of the mixin/function.
43
46
  #
44
47
  # `args`: `Array<(String, Script::Node)>`
45
- # : The arguments for the mixin.
48
+ # : The arguments for the mixin/function.
46
49
  # Each element is a tuple containing the name of the argument
47
50
  # and the parse tree for the default value of the argument.
48
51
  #
49
52
  # `environment`: {Sass::Environment}
50
- # : The environment in which the mixin was defined.
51
- # This is captured so that the mixin can have access
53
+ # : The environment in which the mixin/function was defined.
54
+ # This is captured so that the mixin/function can have access
52
55
  # to local variables defined in its scope.
53
56
  #
54
- # `tree`: {Sass::Tree::Node}
55
- # : The parse tree for the mixin.
56
- Mixin = Struct.new(:name, :args, :environment, :tree)
57
+ # `tree`: `Array<Tree::Node>`
58
+ # : The parse tree for the mixin/function.
59
+ Callable = Struct.new(:name, :args, :environment, :tree)
57
60
 
58
61
  # This class handles the parsing and compilation of the Sass template.
59
62
  # Example usage:
@@ -635,6 +638,8 @@ WARNING
635
638
  parse_mixin_definition(line)
636
639
  elsif directive == "include"
637
640
  parse_mixin_include(line, root)
641
+ elsif directive == "function"
642
+ parse_function(line, root)
638
643
  elsif directive == "for"
639
644
  parse_for(line, root, value)
640
645
  elsif directive == "each"
@@ -665,6 +670,12 @@ WARNING
665
670
  :line => @line + 1) unless line.children.empty?
666
671
  offset = line.offset + line.text.index(value).to_i
667
672
  Tree::WarnNode.new(parse_script(value, :offset => offset))
673
+ elsif directive == "return"
674
+ raise SyntaxError.new("Invalid @return: expected expression.") unless value
675
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
676
+ :line => @line + 1) unless line.children.empty?
677
+ offset = line.offset + line.text.index(value).to_i
678
+ Tree::ReturnNode.new(parse_script(value, :offset => offset))
668
679
  elsif directive == "charset"
669
680
  name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
670
681
  raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
@@ -787,7 +798,6 @@ WARNING
787
798
  offset = line.offset + line.text.size - arg_string.size
788
799
  args = Script::Parser.new(arg_string.strip, @line, offset, @options).
789
800
  parse_mixin_definition_arglist
790
- default_arg_found = false
791
801
  Tree::MixinDefNode.new(name, args)
792
802
  end
793
803
 
@@ -804,6 +814,17 @@ WARNING
804
814
  Tree::MixinNode.new(name, args, keywords)
805
815
  end
806
816
 
817
+ FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
818
+ def parse_function(line, root)
819
+ name, arg_string = line.text.scan(FUNCTION_RE).first
820
+ raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
821
+
822
+ offset = line.offset + line.text.size - arg_string.size
823
+ args = Script::Parser.new(arg_string.strip, @line, offset, @options).
824
+ parse_function_definition_arglist
825
+ Tree::FunctionNode.new(name, args)
826
+ end
827
+
807
828
  def parse_script(script, options = {})
808
829
  line = options[:line] || @line
809
830
  offset = options[:offset] || 0
@@ -2,7 +2,7 @@ require 'set'
2
2
 
3
3
  module Sass
4
4
  # The lexical environment for SassScript.
5
- # This keeps track of variable and mixin definitions.
5
+ # This keeps track of variable, mixin, and function definitions.
6
6
  #
7
7
  # A new environment is created for each level of Sass nesting.
8
8
  # This allows variables to be lexically scoped.
@@ -146,7 +146,10 @@ RUBY
146
146
  # Script::Literal
147
147
  inherited_hash :var
148
148
  # mixin
149
- # Engine::Mixin
149
+ # Sass::Callable
150
150
  inherited_hash :mixin
151
+ # function
152
+ # Sass::Callable
153
+ inherited_hash :function
151
154
  end
152
155
  end
@@ -213,13 +213,19 @@ module Sass::Plugin
213
213
  begin
214
214
  require 'fssm'
215
215
  rescue LoadError => e
216
- e.message << "\n" <<
217
- if File.exists?(scope(".git"))
218
- 'Run "git submodule update --init" to get the recommended version.'
219
- else
220
- 'Run "gem install fssm" to get it.'
221
- end
222
- raise e
216
+ dir = Sass::Util.scope("vendor/fssm/lib")
217
+ if $LOAD_PATH.include?(dir)
218
+ e.message << "\n" <<
219
+ if File.exists?(scope(".git"))
220
+ 'Run "git submodule update --init" to get the recommended version.'
221
+ else
222
+ 'Run "gem install fssm" to get it.'
223
+ end
224
+ raise e
225
+ else
226
+ $LOAD_PATH.unshift dir
227
+ retry
228
+ end
223
229
  end
224
230
 
225
231
  unless individual_files.empty? && FSSM::Backends::Default.name == "FSSM::Backends::FSEvents"
@@ -76,6 +76,10 @@ module Sass
76
76
  def _perform(environment)
77
77
  args = @args.map {|a| a.perform(environment)}
78
78
  keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
79
+ if fn = environment.function(@name)
80
+ return perform_sass_fn(fn, args, keywords)
81
+ end
82
+
79
83
  ruby_name = @name.tr('-', '_')
80
84
  args = construct_ruby_args(ruby_name, args, keywords)
81
85
 
@@ -89,6 +93,8 @@ module Sass
89
93
  raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
90
94
  end
91
95
 
96
+ private
97
+
92
98
  def construct_ruby_args(name, args, keywords)
93
99
  return args if keywords.empty?
94
100
  unless signature = Functions.signature(name.to_sym, args.size, keywords.size)
@@ -113,6 +119,30 @@ module Sass
113
119
 
114
120
  args
115
121
  end
122
+
123
+ def perform_sass_fn(function, args, keywords)
124
+ # TODO: merge with mixin arg evaluation?
125
+ keywords.each do |name, value|
126
+ # TODO: Make this fast
127
+ unless function.args.find {|(var, default)| var.underscored_name == name}
128
+ raise Sass::SyntaxError.new("Function #{@name} doesn't have an argument named $#{name}")
129
+ end
130
+ end
131
+
132
+ environment = function.args.zip(args).
133
+ inject(Sass::Environment.new(function.environment)) do |env, ((var, default), value)|
134
+ env.set_local_var(var.name,
135
+ value || keywords[var.underscored_name] || (default && default.perform(env)))
136
+ raise Sass::SyntaxError.new("Function #{@name} is missing parameter #{var.inspect}.") unless env.var(var.name)
137
+ env
138
+ end
139
+
140
+ val = catch :_sass_return do
141
+ function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, environment)}
142
+ raise Sass::SyntaxError.new("Function #{@name} finished without @return")
143
+ end
144
+ val
145
+ end
116
146
  end
117
147
  end
118
148
  end
@@ -112,6 +112,24 @@ module Sass
112
112
  raise e
113
113
  end
114
114
 
115
+ # Parses the argument list for a function definition.
116
+ #
117
+ # @return [Array<Script::Node>] The root nodes of the arguments.
118
+ # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
119
+ def parse_function_definition_arglist
120
+ args = defn_arglist!(true)
121
+ assert_done
122
+
123
+ args.each do |k, v|
124
+ k.options = @options
125
+ v.options = @options if v
126
+ end
127
+ args
128
+ rescue Sass::SyntaxError => e
129
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
130
+ raise e
131
+ end
132
+
115
133
  # Parses a SassScript expression.
116
134
  #
117
135
  # @overload parse(str, line, offset, filename = nil)
@@ -283,10 +301,16 @@ RUBY
283
301
  node(Script::Funcall.new(tok.value, args, keywords))
284
302
  end
285
303
 
286
- def defn_arglist!(must_have_default)
287
- return [] unless try_tok(:lparen)
304
+ def defn_arglist!(must_have_parens)
305
+ if must_have_parens
306
+ assert_tok(:lparen)
307
+ else
308
+ return [] unless try_tok(:lparen)
309
+ end
288
310
  return [] if try_tok(:rparen)
311
+
289
312
  res = []
313
+ must_have_default = false
290
314
  loop do
291
315
  line = @lexer.line
292
316
  offset = @lexer.offset + 1
@@ -98,8 +98,8 @@ module Sass
98
98
  node << comment
99
99
  end
100
100
 
101
- DIRECTIVES = Set[:mixin, :include, :debug, :warn, :for, :each, :while, :if,
102
- :else, :extend, :import, :media, :charset]
101
+ DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
102
+ :each, :while, :if, :else, :extend, :import, :media, :charset]
103
103
 
104
104
  def directive
105
105
  return unless tok(/@/)
@@ -144,6 +144,17 @@ module Sass
144
144
  node(Sass::Tree::MixinNode.new(name, args, keywords))
145
145
  end
146
146
 
147
+ def function_directive
148
+ name = tok! IDENT
149
+ args = sass_script(:parse_function_definition_arglist)
150
+ ss
151
+ block(node(Sass::Tree::FunctionNode.new(name, args)), :function)
152
+ end
153
+
154
+ def return_directive
155
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
156
+ end
157
+
147
158
  def debug_directive
148
159
  node(Sass::Tree::DebugNode.new(sass_script(:parse)))
149
160
  end
@@ -365,6 +376,7 @@ module Sass
365
376
  end
366
377
 
367
378
  def block_child(context)
379
+ return variable || directive if context == :function
368
380
  return variable || directive || ruleset if context == :stylesheet
369
381
  variable || directive || declaration_or_ruleset
370
382
  end
@@ -20,19 +20,5 @@ module Sass::Tree
20
20
  @list = list
21
21
  super()
22
22
  end
23
-
24
- protected
25
-
26
- # Returns an error message if the given child node is invalid,
27
- # and false otherwise.
28
- #
29
- # {ExtendNode}s are valid within {EachNode}s.
30
- #
31
- # @param child [Tree::Node] A potential child node.
32
- # @return [Boolean, String] Whether or not the child node is valid,
33
- # as well as the error message to display if it is invalid
34
- def invalid_child?(child)
35
- super unless child.is_a?(ExtendNode)
36
- end
37
23
  end
38
24
  end
@@ -0,0 +1,27 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing a function definition.
4
+ #
5
+ # @see Sass::Tree
6
+ class FunctionNode < Node
7
+ # The name of the function.
8
+ # @return [String]
9
+ attr_reader :name
10
+
11
+ # The arguments to the function. Each element is a tuple
12
+ # containing the variable for argument and the parse tree for
13
+ # the default value of the argument
14
+ #
15
+ # @return [Array<Script::Node>]
16
+ attr_reader :args
17
+
18
+ # @param name [String] The function name
19
+ # @param args [Array<(Script::Node, Script::Node)>] The arguments for the function.
20
+ def initialize(name, args)
21
+ @name = name
22
+ @args = args
23
+ super()
24
+ end
25
+ end
26
+ end
27
+ end
@@ -86,29 +86,16 @@ module Sass
86
86
  #
87
87
  # @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
88
88
  # @raise [Sass::SyntaxError] if `child` is invalid
89
- # @see #invalid_child?
90
89
  def <<(child)
91
90
  return if child.nil?
92
91
  if child.is_a?(Array)
93
92
  child.each {|c| self << c}
94
93
  else
95
- check_child! child
96
94
  self.has_children = true
97
95
  @children << child
98
96
  end
99
97
  end
100
98
 
101
- # Raises an error if the given child node is invalid.
102
- #
103
- # @param child [Tree::Node] The child node
104
- # @raise [Sass::SyntaxError] if `child` is invalid
105
- # @see #invalid_child?
106
- def check_child!(child)
107
- if msg = invalid_child?(child)
108
- raise Sass::SyntaxError.new(msg, :line => child.line)
109
- end
110
- end
111
-
112
99
  # Compares this node and another object (only other {Tree::Node}s will be equal).
113
100
  # This does a structural comparison;
114
101
  # if the contents of the nodes and all the child nodes are equivalent,
@@ -227,26 +214,6 @@ module Sass
227
214
  return res if res
228
215
  raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line)
229
216
  end
230
-
231
- # Returns an error message if the given child node is invalid,
232
- # and false otherwise.
233
- #
234
- # By default, all child nodes except those only allowed under specific nodes
235
- # ({Tree::MixinDefNode}, {Tree::ImportNode}, {Tree::ExtendNode}) are valid.
236
- # This is expected to be overriden by subclasses
237
- # for which some children are invalid.
238
- #
239
- # @param child [Tree::Node] A potential child node
240
- # @return [Boolean, String] Whether or not the child node is valid,
241
- # as well as the error message to display if it is invalid
242
- def invalid_child?(child)
243
- case child
244
- when Tree::MixinDefNode
245
- "Mixins may only be defined at the root of a document."
246
- when Tree::ImportNode
247
- "Import directives may only be used at the root of a document."
248
- end
249
- end
250
217
  end
251
218
  end
252
219
  end
@@ -92,18 +92,6 @@ module Sass::Tree
92
92
  "#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip
93
93
  end
94
94
 
95
- # Returns an error message if the given child node is invalid,
96
- # and false otherwise.
97
- #
98
- # {PropNode} only allows other {PropNode}s and {CommentNode}s as children.
99
- # @param child [Tree::Node] A potential child node
100
- # @return [String] An error message if the child is invalid, or nil otherwise
101
- def invalid_child?(child)
102
- if !child.is_a?(PropNode) && !child.is_a?(CommentNode)
103
- "Illegal nesting: Only properties may be nested beneath properties."
104
- end
105
- end
106
-
107
95
  private
108
96
 
109
97
  def check!
@@ -0,0 +1,18 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing returning from a function.
4
+ #
5
+ # @see Sass::Tree
6
+ class ReturnNode < Node
7
+ # The expression to return.
8
+ # @type [Script::Node]
9
+ attr_reader :expr
10
+
11
+ # @param expr [Script::Node] The expression to return
12
+ def initialize(expr)
13
+ @expr = expr
14
+ super()
15
+ end
16
+ end
17
+ end
18
+ end
@@ -16,28 +16,13 @@ module Sass
16
16
  # Runs the dynamic Sass code *and* computes the CSS for the tree.
17
17
  # @see #to_s
18
18
  def render
19
- result, extends = Visitors::Cssize.visit(Visitors::Perform.visit(self))
19
+ Visitors::CheckNesting.visit(self)
20
+ result = Visitors::Perform.visit(self)
21
+ Visitors::CheckNesting.visit(result) # Check again to validate mixins
22
+ result, extends = Visitors::Cssize.visit(result)
20
23
  result = result.do_extend(extends) unless extends.empty?
21
24
  result.to_s
22
25
  end
23
-
24
- # Returns an error message if the given child node is invalid,
25
- # and false otherwise.
26
- #
27
- # Only property nodes are invalid at root level.
28
- #
29
- # @see Node#invalid_child?
30
- def invalid_child?(child)
31
- case child
32
- when Tree::ExtendNode
33
- "Extend directives may only be used within rules."
34
- when Tree::PropNode
35
- "Properties aren't allowed at the root of a document." +
36
- child.pseudo_class_selector_message
37
- else
38
- return
39
- end
40
- end
41
26
  end
42
27
  end
43
28
  end
@@ -32,7 +32,7 @@ module Sass::Tree::Visitors
32
32
  # @param node [Tree::Node] The node to visit.
33
33
  # @return [Object] The return value of the `visit_*` method for this node.
34
34
  def visit(node)
35
- method = "visit_#{node.class.name.gsub(/.*::(.*?)Node$/, '\\1').downcase}"
35
+ method = "visit_#{node_name node}"
36
36
  if self.respond_to?(method)
37
37
  self.send(method, node) {visit_children(node)}
38
38
  else
@@ -53,6 +53,14 @@ module Sass::Tree::Visitors
53
53
  parent.children.map {|c| visit(c)}
54
54
  end
55
55
 
56
+ # Returns the name of a node as used in the `visit_*` method.
57
+ #
58
+ # @param [Tree::Node] node The node.
59
+ # @return [String] The name.
60
+ def node_name(node)
61
+ node.class.name.gsub(/.*::(.*?)Node$/, '\\1').downcase
62
+ end
63
+
56
64
  # `yield`s, then runs the visitor on the `@else` clause if the node has one.
57
65
  # This exists to ensure that the contents of the `@else` clause get visited.
58
66
  def visit_if(node)
@@ -0,0 +1,101 @@
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 visit(node)
6
+ if error = @parent && (
7
+ try_send("invalid_#{node_name @parent}_child?", @parent, node) ||
8
+ try_send("invalid_#{node_name node}_parent?", @parent, node))
9
+ raise Sass::SyntaxError.new(error)
10
+ end
11
+ super
12
+ rescue Sass::SyntaxError => e
13
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
14
+ raise e
15
+ end
16
+
17
+ def visit_children(parent)
18
+ old_parent = @parent
19
+ @parent = parent unless is_any_of?(parent,
20
+ Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
21
+ Sass::Tree::ImportNode, Sass::Tree::MixinNode, Sass::Tree::WhileNode)
22
+ super
23
+ ensure
24
+ @parent = old_parent
25
+ end
26
+
27
+ def visit_root(node)
28
+ yield
29
+ rescue Sass::SyntaxError => e
30
+ e.sass_template ||= node.template
31
+ raise e
32
+ end
33
+
34
+ def visit_import(node)
35
+ yield
36
+ rescue Sass::SyntaxError => e
37
+ e.modify_backtrace(:filename => node.children.first.filename)
38
+ e.add_backtrace(:filename => node.filename, :line => node.line)
39
+ raise e
40
+ end
41
+
42
+ def invalid_charset_parent?(parent, child)
43
+ "@charset may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
44
+ end
45
+
46
+ def invalid_extend_parent?(parent, child)
47
+ unless is_any_of?(parent, Sass::Tree::RuleNode, Sass::Tree::MixinDefNode)
48
+ "Extend directives may only be used within rules."
49
+ end
50
+ end
51
+
52
+ def invalid_function_parent?(parent, child)
53
+ "Functions may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
54
+ end
55
+
56
+ def invalid_function_child?(parent, child)
57
+ unless is_any_of?(child,
58
+ Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::EachNode,
59
+ Sass::Tree::ForNode, Sass::Tree::IfNode, Sass::Tree::ReturnNode,
60
+ Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::WhileNode)
61
+ "Functions can only contain variable declarations and control directives."
62
+ end
63
+ end
64
+
65
+ def invalid_import_parent?(parent, child)
66
+ "Import directives may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
67
+ end
68
+
69
+ def invalid_mixindef_parent?(parent, child)
70
+ "Mixins may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
71
+ end
72
+
73
+ def invalid_prop_child?(parent, child)
74
+ unless is_any_of?(child, Sass::Tree::CommentNode, Sass::Tree::PropNode)
75
+ "Illegal nesting: Only properties may be nested beneath properties."
76
+ end
77
+ end
78
+
79
+ def invalid_prop_parent?(parent, child)
80
+ unless is_any_of?(parent,
81
+ Sass::Tree::RuleNode, Sass::Tree::PropNode,
82
+ Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode)
83
+ "Properties are only allowed within rules, directives, or other properties." + child.pseudo_class_selector_message
84
+ end
85
+ end
86
+
87
+ def invalid_return_parent?(parent, child)
88
+ "@return may only be used within a function." unless parent.is_a?(Sass::Tree::FunctionNode)
89
+ end
90
+
91
+ private
92
+
93
+ def is_any_of?(val, *classes)
94
+ classes.any? {|c| val.is_a?(c)}
95
+ end
96
+
97
+ def try_send(method, *args, &block)
98
+ return unless respond_to?(method)
99
+ send(method, *args, &block)
100
+ end
101
+ end
@@ -110,6 +110,14 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
110
110
  "#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
111
111
  end
112
112
 
113
+ def visit_function(node)
114
+ args = node.args.map do |v, d|
115
+ d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
116
+ end.join(", ")
117
+
118
+ "#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"
119
+ end
120
+
113
121
  def visit_if(node)
114
122
  name =
115
123
  if !@is_else; "if"
@@ -167,6 +175,10 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
167
175
  res + yield.rstrip + semi + "\n"
168
176
  end
169
177
 
178
+ def visit_return(node)
179
+ "#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n"
180
+ end
181
+
170
182
  def visit_rule(node)
171
183
  if @format == :sass
172
184
  name = selector_to_sass(node.rule)
@@ -123,11 +123,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
123
123
 
124
124
  # Asserts that all the mixin's children are valid in their new location.
125
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
126
+ # Don't use #visit_children to avoid adding the mixin node to the list of parents.
127
+ node.children.map {|c| visit(c)}.flatten
131
128
  rescue Sass::SyntaxError => e
132
129
  e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
133
130
  e.add_backtrace(:filename => node.filename, :line => node.line)
@@ -1,9 +1,16 @@
1
1
  # A visitor for converting a dynamic Sass tree into a static Sass tree.
2
2
  class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @param environment [Sass::Environment] The lexical environment.
5
+ # @return [Tree::Node] The resulting tree of static nodes.
6
+ def self.visit(root, environment = Sass::Environment.new)
7
+ new(environment).send(:visit, root)
8
+ end
9
+
3
10
  protected
4
11
 
5
- def initialize
6
- @environment = Sass::Environment.new
12
+ def initialize(env)
13
+ @environment = env
7
14
  end
8
15
 
9
16
  # If an exception is raised, this add proper metadata to the backtrace.
@@ -18,7 +25,6 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
18
25
  def visit_children(parent)
19
26
  with_environment Sass::Environment.new(@environment) do
20
27
  parent.children = super.flatten
21
- parent.children.each {|c| parent.check_child! c}
22
28
  parent
23
29
  end
24
30
  end
@@ -100,6 +106,13 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
100
106
  end
101
107
  end
102
108
 
109
+ # Loads the function into the environment.
110
+ def visit_function(node)
111
+ @environment.set_function(node.name,
112
+ Sass::Callable.new(node.name, node.args, @environment, node.children))
113
+ []
114
+ end
115
+
103
116
  # Runs the child nodes if the conditional expression is true;
104
117
  # otherwise, tries the else nodes.
105
118
  def visit_if(node)
@@ -135,7 +148,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
135
148
  # Loads a mixin into the environment.
136
149
  def visit_mixindef(node)
137
150
  @environment.set_mixin(node.name,
138
- Sass::Mixin.new(node.name, node.args, @environment, node.children))
151
+ Sass::Callable.new(node.name, node.args, @environment, node.children))
139
152
  []
140
153
  end
141
154
 
@@ -206,6 +219,11 @@ END
206
219
  yield
207
220
  end
208
221
 
222
+ # Returns the value of the expression.
223
+ def visit_return(node)
224
+ throw :_sass_return, node.expr.perform(@environment)
225
+ end
226
+
209
227
  # Runs SassScript interpolation in the selector,
210
228
  # and then parses the result into a {Sass::Selector::CommaSequence}.
211
229
  def visit_rule(node)
@@ -901,6 +901,42 @@ foo {
901
901
  SCSS
902
902
  end
903
903
 
904
+ def test_argless_function_definition
905
+ assert_renders <<SASS, <<SCSS
906
+ @function foo()
907
+ $var: 1 + 1
908
+ @return $var
909
+ SASS
910
+ @function foo() {
911
+ $var: 1 + 1;
912
+ @return $var; }
913
+ SCSS
914
+ end
915
+
916
+ def test_function_definition_without_defaults
917
+ assert_renders <<SASS, <<SCSS
918
+ @function foo($var1, $var2)
919
+ @if $var1
920
+ @return $var1 + $var2
921
+ SASS
922
+ @function foo($var1, $var2) {
923
+ @if $var1 {
924
+ @return $var1 + $var2; } }
925
+ SCSS
926
+ end
927
+
928
+ def test_function_definition_with_defaults
929
+ assert_renders <<SASS, <<SCSS
930
+ @function foo($var1, $var2: foo)
931
+ @if $var1
932
+ @return $var1 + $var2
933
+ SASS
934
+ @function foo($var1, $var2: foo) {
935
+ @if $var1 {
936
+ @return $var1 + $var2; } }
937
+ SCSS
938
+ end
939
+
904
940
  def test_variable_definition
905
941
  assert_renders <<SASS, <<SCSS
906
942
  $var1: 12px + 15px
@@ -39,8 +39,8 @@ MSG
39
39
  "a\n b: c;" => 'Invalid CSS after "c": expected expression (e.g. 1px, bold), was ";"',
40
40
  ".foo ^bar\n a: b" => ['Invalid CSS after ".foo ": expected selector, was "^bar"', 1],
41
41
  "a\n @extend .foo ^bar" => 'Invalid CSS after ".foo ": expected selector, was "^bar"',
42
- "a: b" => 'Properties aren\'t allowed at the root of a document.',
43
- ":a b" => 'Properties aren\'t allowed at the root of a document.',
42
+ "a: b" => 'Properties are only allowed within rules, directives, or other properties.',
43
+ ":a b" => 'Properties are only allowed within rules, directives, or other properties.',
44
44
  "!" => 'Invalid variable: "!".',
45
45
  "$a" => 'Invalid variable: "$a".',
46
46
  "! a" => 'Invalid variable: "! a".',
@@ -87,12 +87,26 @@ MSG
87
87
  "=a(,)" => 'Invalid CSS after "(": expected variable (e.g. $foo), was ",)"',
88
88
  "=a($)" => 'Invalid CSS after "(": expected variable (e.g. $foo), was "$)"',
89
89
  "=a($foo bar)" => 'Invalid CSS after "($foo ": expected ")", was "bar)"',
90
- "=foo\n bar: baz\n+foo" => ["Properties aren't allowed at the root of a document.", 2],
90
+ "=foo\n bar: baz\n+foo" => ["Properties are only allowed within rules, directives, or other properties.", 2],
91
91
  "a-\#{$b\n c: d" => ['Invalid CSS after "a-#{$b": expected "}", was ""', 1],
92
92
  "=a($b = 1, $c)" => "Required argument $c must come before any optional arguments.",
93
93
  "=a($b = 1)\n a: $b\ndiv\n +a(1,2)" => "Mixin a takes 1 argument but 2 were passed.",
94
94
  "=a($b: 1)\n a: $b\ndiv\n +a(1,$c: 3)" => "Mixin a doesn't have an argument named $c",
95
95
  "=a($b)\n a: $b\ndiv\n +a" => "Mixin a is missing parameter $b.",
96
+ "@function foo()\n 1 + 2" => "Functions can only contain variable declarations and control directives.",
97
+ "@function foo()\n foo: bar" => "Functions can only contain variable declarations and control directives.",
98
+ "@function foo()\n foo: bar\n @return 3" => ["Functions can only contain variable declarations and control directives.", 2],
99
+ "@function foo\n @return 1" => ['Invalid CSS after "": expected "(", was ""', 1],
100
+ "@function foo(\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was ""', 1],
101
+ "@function foo(b)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "b)"', 1],
102
+ "@function foo(,)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was ",)"', 1],
103
+ "@function foo($)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "$)"', 1],
104
+ "@function foo()\n @return" => 'Invalid @return: expected expression.',
105
+ "@function foo()\n @return 1\n $var: val" => 'Illegal nesting: Nothing may be nested beneath return directives.',
106
+ "foo\n @function bar()\n @return 1" => ['Functions may only be defined at the root of a document.', 2],
107
+ "@return 1" => '@return may only be used within a function.',
108
+ "@if true\n @return 1" => '@return may only be used within a function.',
109
+ "@mixin foo\n @return 1\n@include foo" => ['@return may only be used within a function.', 2],
96
110
  "@else\n a\n b: c" => ["@else must come after @if.", 1],
97
111
  "@if false\n@else foo" => "Invalid else directive '@else foo': expected 'if <expr>'.",
98
112
  "@if false\n@else if " => "Invalid else directive '@else if': expected 'if <expr>'.",
@@ -511,7 +525,7 @@ SASS
511
525
  rescue Sass::SyntaxError => e
512
526
  assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e, opts).split("\n")[0..11].join("\n"))
513
527
  /*
514
- Syntax error: Properties aren't allowed at the root of a document.
528
+ Syntax error: Properties are only allowed within rules, directives, or other properties.
515
529
  on line 4 of test_cssize_exception_css_inline.sass
516
530
 
517
531
  1: .filler
@@ -1127,6 +1141,90 @@ a
1127
1141
  SASS
1128
1142
  end
1129
1143
 
1144
+ def test_basic_function
1145
+ assert_equal(<<CSS, render(<<SASS))
1146
+ bar {
1147
+ a: 3; }
1148
+ CSS
1149
+ @function foo()
1150
+ @return 1 + 2
1151
+
1152
+ bar
1153
+ a: foo()
1154
+ SASS
1155
+ end
1156
+
1157
+ def test_function_args
1158
+ assert_equal(<<CSS, render(<<SASS))
1159
+ bar {
1160
+ a: 3; }
1161
+ CSS
1162
+ @function plus($var1, $var2)
1163
+ @return $var1 + $var2
1164
+
1165
+ bar
1166
+ a: plus(1, 2)
1167
+ SASS
1168
+ end
1169
+
1170
+ def test_function_arg_default
1171
+ assert_equal(<<CSS, render(<<SASS))
1172
+ bar {
1173
+ a: 3; }
1174
+ CSS
1175
+ @function plus($var1, $var2: 2)
1176
+ @return $var1 + $var2
1177
+
1178
+ bar
1179
+ a: plus(1)
1180
+ SASS
1181
+ end
1182
+
1183
+ def test_function_arg_keyword
1184
+ assert_equal(<<CSS, render(<<SASS))
1185
+ bar {
1186
+ a: 1bar; }
1187
+ CSS
1188
+ @function plus($var1: 1, $var2: 2)
1189
+ @return $var1 + $var2
1190
+
1191
+ bar
1192
+ a: plus($var2: bar)
1193
+ SASS
1194
+ end
1195
+
1196
+ def test_function_with_if
1197
+ assert_equal(<<CSS, render(<<SASS))
1198
+ bar {
1199
+ a: foo;
1200
+ b: bar; }
1201
+ CSS
1202
+ @function my-if($cond, $val1, $val2)
1203
+ @if $cond
1204
+ @return $val1
1205
+ @else
1206
+ @return $val2
1207
+
1208
+ bar
1209
+ a: my-if(true, foo, bar)
1210
+ b: my-if(false, foo, bar)
1211
+ SASS
1212
+ end
1213
+
1214
+ def test_function_with_var
1215
+ assert_equal(<<CSS, render(<<SASS))
1216
+ bar {
1217
+ a: 1; }
1218
+ CSS
1219
+ @function foo($val1, $val2)
1220
+ $intermediate: $val1 + $val2
1221
+ @return $intermediate/3
1222
+
1223
+ bar
1224
+ a: foo(1, 2)
1225
+ SASS
1226
+ end
1227
+
1130
1228
  def test_interpolation
1131
1229
  assert_equal("a-1 {\n b-2-3: c-3; }\n", render(<<SASS))
1132
1230
  $a: 1
@@ -699,6 +699,38 @@ CSS
699
699
  SCSS
700
700
  end
701
701
 
702
+ ## Functions
703
+
704
+ def test_basic_function
705
+ assert_equal(<<CSS, render(<<SASS))
706
+ bar {
707
+ a: 3; }
708
+ CSS
709
+ @function foo() {
710
+ @return 1 + 2;
711
+ }
712
+
713
+ bar {
714
+ a: foo();
715
+ }
716
+ SASS
717
+ end
718
+
719
+ def test_function_args
720
+ assert_equal(<<CSS, render(<<SASS))
721
+ bar {
722
+ a: 3; }
723
+ CSS
724
+ @function plus($var1, $var2) {
725
+ @return $var1 + $var2;
726
+ }
727
+
728
+ bar {
729
+ a: plus(1, 2);
730
+ }
731
+ SASS
732
+ end
733
+
702
734
  ## Interpolation
703
735
 
704
736
  def test_basic_selector_interpolation
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0.alpha.50
4
+ version: 3.1.0.alpha.51
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Weizenbaum
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2010-12-05 00:00:00 -05:00
14
+ date: 2010-12-07 00:00:00 -05:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
@@ -133,6 +133,9 @@ files:
133
133
  - lib/sass/tree/visitors/cssize.rb
134
134
  - lib/sass/tree/visitors/perform.rb
135
135
  - lib/sass/tree/visitors/to_css.rb
136
+ - lib/sass/tree/visitors/check_nesting.rb
137
+ - lib/sass/tree/function_node.rb
138
+ - lib/sass/tree/return_node.rb
136
139
  - lib/sass/util.rb
137
140
  - lib/sass/util/subset_map.rb
138
141
  - lib/sass/version.rb