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

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.
@@ -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