sass 3.3.0.alpha.253 → 3.3.0.alpha.255

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.
data/REVISION CHANGED
@@ -1 +1 @@
1
- b6f71a816b6d7fd3c54def0dbb2eb4266c1585d1
1
+ a4e9233341aeaf94bd00b4c85b95e004c94df2f7
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.3.0.alpha.253
1
+ 3.3.0.alpha.255
@@ -1 +1 @@
1
- 07 September 2013 00:38:13 GMT
1
+ 18 September 2013 22:11:11 GMT
@@ -1060,9 +1060,10 @@ WARNING
1060
1060
  raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
1061
1061
 
1062
1062
  offset = line.offset + line.text.size - arg_string.size
1063
- args, keywords, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
1064
- parse_mixin_include_arglist
1065
- Tree::MixinNode.new(name, args, keywords, splat)
1063
+ args, keywords, splat, kwarg_splat =
1064
+ Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
1065
+ parse_mixin_include_arglist
1066
+ Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat)
1066
1067
  end
1067
1068
 
1068
1069
  FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
@@ -194,6 +194,9 @@ module Sass::Script
194
194
  # \{#map_has_key map-has-key($key)}
195
195
  # : Returns whether a map has a value associated with a given key.
196
196
  #
197
+ # \{#keywords keywords($args)}
198
+ # : Returns the keywords passed to a function that takes variable arguments.
199
+ #
197
200
  # ## Introspection Functions
198
201
  #
199
202
  # \{#feature_exists feature-exists($feature)}
@@ -367,6 +370,11 @@ module Sass::Script
367
370
  class EvaluationContext
368
371
  include Functions
369
372
 
373
+ # The human-readable names for [Sass::Script::Value::Base]. The default is
374
+ # just the downcased name of the type. The default is the downcased type
375
+ # name.
376
+ TYPE_NAMES = {:ArgList => 'variable argument list'}
377
+
370
378
  # The global environment.
371
379
  #
372
380
  # @return [Environment]
@@ -401,7 +409,7 @@ module Sass::Script
401
409
  klass = Sass::Script::Value.const_get(type)
402
410
  return if value.is_a?(klass)
403
411
  return if value.is_a?(Sass::Script::Value::List) && type == :Map && value.is_pseudo_map?
404
- err = "#{value.inspect} is not a #{type.to_s.downcase}"
412
+ err = "#{value.inspect} is not a #{TYPE_NAMES[type] || type.to_s.downcase}"
405
413
  err = "$#{name.to_s.gsub('_', '-')}: " + err if name
406
414
  raise ArgumentError.new(err)
407
415
  end
@@ -1859,6 +1867,26 @@ module Sass::Script
1859
1867
  end
1860
1868
  declare :map_has_key, [:map, :key]
1861
1869
 
1870
+ # Returns the map of named arguments passed to a function or mixin that
1871
+ # takes a variable argument list. The argument names are strings, and they
1872
+ # do not contain the leading `$`.
1873
+ #
1874
+ # @example
1875
+ # @mixin foo($args...) {
1876
+ # @debug keywords($args); //=> (arg1: val, arg2: val)
1877
+ # }
1878
+ #
1879
+ # @include foo($arg1: val, $arg2: val);
1880
+ # @overload keywords($args)
1881
+ # @param $args [Sass::Script::Value::ArgList]
1882
+ # @return [Sass::Script::Value::Map]
1883
+ # @raise [ArgumentError] if `$args` isn't a variable argument list
1884
+ def keywords(args)
1885
+ assert_type args, :ArgList
1886
+ Sass::Script::Value::Map.new(Sass::Util.map_keys(args.keywords) {|k| Sass::Script::String.new(k)})
1887
+ end
1888
+ declare :keywords, [:args]
1889
+
1862
1890
  # Returns one of two values, depending on whether or not `$condition` is
1863
1891
  # true. Just like in `@if`, all values other than `false` and `null` are
1864
1892
  # considered to be true.
@@ -1917,6 +1945,7 @@ module Sass::Script
1917
1945
  name.value,
1918
1946
  args.map {|a| Sass::Script::Tree::Literal.new(a)},
1919
1947
  Sass::Util.map_vals(kwargs) {|v| Sass::Script::Tree::Literal.new(v)},
1948
+ nil,
1920
1949
  nil)
1921
1950
  funcall.options = options
1922
1951
  funcall.perform(environment)
@@ -81,14 +81,14 @@ module Sass
81
81
 
82
82
  # Parses the argument list for a mixin include.
83
83
  #
84
- # @return [(Array<Script::Tree::Node>, {String => Script::Tree::Node}, Script::Tree::Node)]
84
+ # @return [(Array<Script::Tree::Node>, {String => Script::Tree::Node}, Script::Tree::Node, Script::Tree::Node)]
85
85
  # The root nodes of the positional arguments, keyword arguments, and
86
- # splat argument. Keyword arguments are in a hash from names to values.
86
+ # splat argument(s). Keyword arguments are in a hash from names to values.
87
87
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
88
88
  def parse_mixin_include_arglist
89
89
  args, keywords = [], {}
90
90
  if try_tok(:lparen)
91
- args, keywords, splat = mixin_arglist
91
+ args, keywords, splat, kwarg_splat = mixin_arglist
92
92
  assert_tok(:rparen)
93
93
  end
94
94
  assert_done
@@ -96,7 +96,8 @@ module Sass
96
96
  args.each {|a| a.options = @options}
97
97
  keywords.each {|k, v| v.options = @options}
98
98
  splat.options = @options if splat
99
- return args, keywords, splat
99
+ kwarg_splat.options = @options if kwarg_splat
100
+ return args, keywords, splat, kwarg_splat
100
101
  rescue Sass::SyntaxError => e
101
102
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
102
103
  raise e
@@ -366,9 +367,9 @@ RUBY
366
367
 
367
368
  def funcall
368
369
  return raw unless tok = try_tok(:funcall)
369
- args, keywords, splat = fn_arglist
370
+ args, keywords, splat, kwarg_splat = fn_arglist
370
371
  assert_tok(:rparen)
371
- node(Script::Tree::Funcall.new(tok.value, args, keywords, splat),
372
+ node(Script::Tree::Funcall.new(tok.value, args, keywords, splat, kwarg_splat),
372
373
  tok.source_range.start_pos, source_position)
373
374
  end
374
375
 
@@ -433,7 +434,13 @@ RUBY
433
434
  raise SyntaxError.new("Positional arguments must come before keyword arguments.")
434
435
  end
435
436
 
436
- return args, keywords, e if try_tok(:splat)
437
+ if try_tok(:splat)
438
+ splat = e
439
+ return args, keywords, splat unless try_tok(:comma)
440
+ kwarg_splat = assert_expr(subexpr, description)
441
+ assert_tok(:splat)
442
+ return args, keywords, splat, kwarg_splat
443
+ end
437
444
  args << e
438
445
  end
439
446
 
@@ -517,6 +524,7 @@ RUBY
517
524
  :default => "expression (e.g. 1px, bold)",
518
525
  :mixin_arglist => "mixin argument",
519
526
  :fn_arglist => "function argument",
527
+ :splat => "...",
520
528
  }
521
529
 
522
530
  def assert_expr(name, expected = nil)
@@ -23,20 +23,33 @@ module Sass::Script::Tree
23
23
  # @return [{String => Node}]
24
24
  attr_reader :keywords
25
25
 
26
- # The splat argument for this function, if one exists.
26
+ # The first splat argument for this function, if one exists.
27
+ #
28
+ # This could be a list of positional arguments, a map of keyword
29
+ # arguments, or an arglist containing both.
27
30
  #
28
31
  # @return [Node?]
29
32
  attr_accessor :splat
30
33
 
34
+ # The second splat argument for this function, if one exists.
35
+ #
36
+ # If this exists, it's always a map of keyword arguments, and
37
+ # \{#splat} is always either a list or an arglist.
38
+ #
39
+ # @return [Node?]
40
+ attr_accessor :kwarg_splat
41
+
31
42
  # @param name [String] See \{#name}
32
43
  # @param args [Array<Node>] See \{#args}
33
44
  # @param keywords [Sass::Util::NormalizedMap<Node>] See \{#keywords}
34
45
  # @param splat [Node] See \{#splat}
35
- def initialize(name, args, keywords, splat)
46
+ # @param kwarg_splat [Node] See \{#kwarg_splat}
47
+ def initialize(name, args, keywords, splat, kwarg_splat)
36
48
  @name = name
37
49
  @args = args
38
50
  @keywords = keywords
39
51
  @splat = splat
52
+ @kwarg_splat = kwarg_splat
40
53
  super()
41
54
  end
42
55
 
@@ -48,6 +61,7 @@ module Sass::Script::Tree
48
61
  if self.splat
49
62
  splat = (args.empty? && keywords.empty?) ? "" : ", "
50
63
  splat = "#{splat}#{self.splat.inspect}..."
64
+ splat = "#{splat}, #{kwarg_splat.inspect}..." if kwarg_splat
51
65
  end
52
66
  "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
53
67
  end
@@ -66,6 +80,7 @@ module Sass::Script::Tree
66
80
  if self.splat
67
81
  splat = (args.empty? && keywords.empty?) ? "" : ", "
68
82
  splat = "#{splat}#{arg_to_sass[self.splat]}..."
83
+ splat = "#{splat}, #{arg_to_sass[kwarg_splat]}..." if kwarg_splat
69
84
  end
70
85
  "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
71
86
  end
@@ -77,6 +92,7 @@ module Sass::Script::Tree
77
92
  def children
78
93
  res = @args + @keywords.values
79
94
  res << @splat if @splat
95
+ res << @kwarg_splat if @kwarg_splat
80
96
  res
81
97
  end
82
98
 
@@ -99,14 +115,14 @@ module Sass::Script::Tree
99
115
  # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
100
116
  def _perform(environment)
101
117
  args = @args.map {|a| a.perform(environment)}
102
- splat = @splat.perform(environment) if @splat
118
+ splat = Sass::Tree::Visitors::Perform.perform_splat(@splat, @kwarg_splat, environment)
119
+ keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
103
120
  if fn = environment.function(@name)
104
- keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
105
121
  return perform_sass_fn(fn, args, keywords, splat, environment)
106
122
  end
107
123
 
108
124
  ruby_name = @name.tr('-', '_')
109
- args = construct_ruby_args(ruby_name, args, splat, environment)
125
+ args = construct_ruby_args(ruby_name, args, keywords, splat, environment)
110
126
 
111
127
  unless Sass::Script::Functions.callable?(ruby_name)
112
128
  opts(to_literal(args))
@@ -179,23 +195,20 @@ module Sass::Script::Tree
179
195
 
180
196
  private
181
197
 
182
- def construct_ruby_args(name, args, splat, environment)
198
+ def construct_ruby_args(name, args, keywords, splat, environment)
183
199
  args += splat.to_a if splat
184
200
 
185
201
  # If variable arguments were passed, there won't be any explicit keywords.
186
- if splat.is_a?(Sass::Script::Value::ArgList)
187
- kwargs_size = splat.keywords.size
188
- splat.keywords_accessed = false
189
- else
190
- kwargs_size = @keywords.size
202
+ if splat && !splat.keywords.empty?
203
+ old_keywords_accessed = splat.keywords_accessed
204
+ keywords = splat.keywords
205
+ splat.keywords_accessed = old_keywords_accessed
191
206
  end
192
207
 
193
- unless signature = Sass::Script::Functions.signature(name.to_sym, args.size, kwargs_size)
194
- return args if @keywords.empty?
208
+ unless signature = Sass::Script::Functions.signature(name.to_sym, args.size, keywords.size)
209
+ return args if keywords.empty?
195
210
  raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
196
211
  end
197
- keywords = splat.is_a?(Sass::Script::Value::ArgList) ? splat.keywords :
198
- Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
199
212
 
200
213
  # If the user passes more non-keyword args than the function expects,
201
214
  # but it does expect keyword args, Ruby's arg handling won't raise an error.
@@ -28,10 +28,5 @@ module Sass::Script::Value
28
28
  @keywords_accessed = true
29
29
  @keywords
30
30
  end
31
-
32
- # @see Base#children
33
- def children
34
- super + @keywords.values
35
- end
36
31
  end
37
32
  end
@@ -209,9 +209,9 @@ module Sass
209
209
 
210
210
  def include_directive(start_pos)
211
211
  name = tok! IDENT
212
- args, keywords, splat = sass_script(:parse_mixin_include_arglist)
212
+ args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
213
213
  ss
214
- include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat), start_pos)
214
+ include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
215
215
  if tok?(/\{/)
216
216
  include_node.has_children = true
217
217
  block(include_node, :directive)
@@ -19,20 +19,33 @@ module Sass::Tree
19
19
  # @return [{String => Script::Tree::Node}]
20
20
  attr_accessor :keywords
21
21
 
22
- # The splat argument for this mixin, if one exists.
22
+ # The first splat argument for this mixin, if one exists.
23
23
  #
24
- # @return [Script::Tree::Node?]
24
+ # This could be a list of positional arguments, a map of keyword
25
+ # arguments, or an arglist containing both.
26
+ #
27
+ # @return [Node?]
25
28
  attr_accessor :splat
26
29
 
30
+ # The second splat argument for this mixin, if one exists.
31
+ #
32
+ # If this exists, it's always a map of keyword arguments, and
33
+ # \{#splat} is always either a list or an arglist.
34
+ #
35
+ # @return [Node?]
36
+ attr_accessor :kwarg_splat
37
+
27
38
  # @param name [String] The name of the mixin
28
39
  # @param args [Array<Script::Tree::Node>] See \{#args}
29
40
  # @param splat [Script::Tree::Node] See \{#splat}
41
+ # @param kwarg_splat [Script::Tree::Node] See \{#kwarg_splat}
30
42
  # @param keywords [{String => Script::Tree::Node}] See \{#keywords}
31
- def initialize(name, args, keywords, splat)
43
+ def initialize(name, args, keywords, splat, kwarg_splat)
32
44
  @name = name
33
45
  @args = args
34
46
  @keywords = keywords
35
47
  @splat = splat
48
+ @kwarg_splat = kwarg_splat
36
49
  super()
37
50
  end
38
51
  end
@@ -208,6 +208,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
208
208
  if node.splat
209
209
  splat = (args.empty? && keywords.empty?) ? "" : ", "
210
210
  splat = "#{splat}#{arg_to_sass[node.splat]}..."
211
+ splat = "#{splat}, #{node.kwarg_splat.inspect}..." if node.kwarg_splat
211
212
  end
212
213
  arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
213
214
  end
@@ -1,89 +1,135 @@
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 = nil)
7
- new(environment).send(:visit, root)
8
- end
3
+ class << self
4
+ # @param root [Tree::Node] The root node of the tree to visit.
5
+ # @param environment [Sass::Environment] The lexical environment.
6
+ # @return [Tree::Node] The resulting tree of static nodes.
7
+ def visit(root, environment = nil)
8
+ new(environment).send(:visit, root)
9
+ end
9
10
 
10
- # @api private
11
- def self.perform_arguments(callable, args, keywords, splat)
12
- desc = "#{callable.type.capitalize} #{callable.name}"
13
- downcase_desc = "#{callable.type} #{callable.name}"
11
+ # @api private
12
+ def perform_arguments(callable, args, keywords, splat)
13
+ desc = "#{callable.type.capitalize} #{callable.name}"
14
+ downcase_desc = "#{callable.type} #{callable.name}"
14
15
 
15
- begin
16
- unless keywords.empty?
17
- unknown_args = Sass::Util.array_minus(keywords.keys,
18
- callable.args.map {|var| var.first.underscored_name})
19
- if callable.splat && unknown_args.include?(callable.splat.underscored_name)
20
- raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} cannot be used as a named argument.")
21
- elsif unknown_args.any?
22
- description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
23
- raise Sass::SyntaxError.new("#{desc} doesn't have #{description} #{unknown_args.map {|name| "$#{name}"}.join ', '}.")
16
+ # If variable arguments were passed, there won't be any explicit keywords.
17
+ if splat && !splat.keywords.empty?
18
+ old_keywords_accessed = splat.keywords_accessed
19
+ keywords = splat.keywords
20
+ splat.keywords_accessed = old_keywords_accessed
21
+ end
22
+
23
+ begin
24
+ unless keywords.empty?
25
+ unknown_args = Sass::Util.array_minus(keywords.keys,
26
+ callable.args.map {|var| var.first.underscored_name})
27
+ if callable.splat && unknown_args.include?(callable.splat.underscored_name)
28
+ raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} cannot be used as a named argument.")
29
+ elsif unknown_args.any?
30
+ description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
31
+ raise Sass::SyntaxError.new("#{desc} doesn't have #{description} #{unknown_args.map {|name| "$#{name}"}.join ', '}.")
32
+ end
24
33
  end
34
+ rescue Sass::SyntaxError => keyword_exception
25
35
  end
26
- rescue Sass::SyntaxError => keyword_exception
27
- end
28
36
 
29
- # If there's no splat, raise the keyword exception immediately. The actual
30
- # raising happens in the ensure clause at the end of this function.
31
- return if keyword_exception && !callable.splat
37
+ # If there's no splat, raise the keyword exception immediately. The actual
38
+ # raising happens in the ensure clause at the end of this function.
39
+ return if keyword_exception && !callable.splat
32
40
 
33
- if args.size > callable.args.size && !callable.splat
34
- takes = callable.args.size
35
- passed = args.size
36
- raise Sass::SyntaxError.new(
37
- "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
38
- "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
39
- end
41
+ if args.size > callable.args.size && !callable.splat
42
+ takes = callable.args.size
43
+ passed = args.size
44
+ raise Sass::SyntaxError.new(
45
+ "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
46
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
47
+ end
40
48
 
41
- splat_sep = :comma
42
- if splat
43
- args += splat.to_a
44
- splat_sep = splat.separator if splat.is_a?(Sass::Script::Value::List)
45
- # If the splat argument exists, there won't be any keywords passed in
46
- # manually, so we can safely overwrite rather than merge here.
47
- keywords = splat.keywords if splat.is_a?(Sass::Script::Value::ArgList)
48
- end
49
+ splat_sep = :comma
50
+ if splat
51
+ args += splat.to_a
52
+ splat_sep = splat.separator
53
+ end
54
+
55
+ keywords = keywords.dup
56
+ env = Sass::Environment.new(callable.environment)
57
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
58
+ if value && keywords.include?(var.underscored_name)
59
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} both by position and by name.")
60
+ end
61
+
62
+ value ||= keywords.delete(var.underscored_name)
63
+ value ||= default && default.perform(env)
64
+ raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
65
+ env.set_local_var(var.name, value)
66
+ end
49
67
 
50
- keywords = keywords.dup
51
- env = Sass::Environment.new(callable.environment)
52
- callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
53
- if value && keywords.include?(var.underscored_name)
54
- raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} both by position and by name.")
68
+ if callable.splat
69
+ rest = args[callable.args.length..-1] || []
70
+ arg_list = Sass::Script::Value::ArgList.new(rest, keywords.dup, splat_sep)
71
+ arg_list.options = env.options
72
+ env.set_local_var(callable.splat.name, arg_list)
55
73
  end
56
74
 
57
- value ||= keywords.delete(var.underscored_name)
58
- value ||= default && default.perform(env)
59
- raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
60
- env.set_local_var(var.name, value)
75
+ yield env
76
+ rescue Exception => e
77
+ ensure
78
+ # If there's a keyword exception, we don't want to throw it immediately,
79
+ # because the invalid keywords may be part of a glob argument that should be
80
+ # passed on to another function. So we only raise it if we reach the end of
81
+ # this function *and* the keywords attached to the argument list glob object
82
+ # haven't been accessed.
83
+ #
84
+ # The keyword exception takes precedence over any Sass errors, but not over
85
+ # non-Sass exceptions.
86
+ if keyword_exception &&
87
+ !(arg_list && arg_list.keywords_accessed) &&
88
+ (e.nil? || e.is_a?(Sass::SyntaxError))
89
+ raise keyword_exception
90
+ elsif e
91
+ raise e
92
+ end
61
93
  end
62
94
 
63
- if callable.splat
64
- rest = args[callable.args.length..-1]
65
- arg_list = Sass::Script::Value::ArgList.new(rest, keywords.dup, splat_sep)
66
- arg_list.options = env.options
67
- env.set_local_var(callable.splat.name, arg_list)
95
+ # @api private
96
+ # @return [Sass::Script::Value::ArgList]
97
+ def perform_splat(splat, kwarg_splat, environment)
98
+ return unless splat
99
+ splat = splat.perform(environment)
100
+ unless kwarg_splat
101
+ return splat if splat.is_a?(Sass::Script::Value::ArgList)
102
+ if splat.is_a?(Sass::Script::Value::Map)
103
+ args = []
104
+ kwargs = arg_hash(splat)
105
+ else
106
+ args = splat.to_a
107
+ kwargs = {}
108
+ end
109
+ return Sass::Script::Value::ArgList.new(args, kwargs, splat.separator || :comma)
110
+ end
111
+
112
+ kwarg_splat = kwarg_splat.perform(environment)
113
+ unless kwarg_splat.is_a?(Sass::Script::Value::Map)
114
+ raise Sass::SyntaxError.new("Variable keyword arguments must be a map (was #{kwarg_splat.inspect}).")
115
+ end
116
+
117
+ if splat.is_a?(Sass::Script::Value::ArgList)
118
+ return Sass::Script::Value::ArgList.new(
119
+ splat.value, splat.keywords.merge(arg_hash(kwarg_splat)), splat.separator)
120
+ else
121
+ return Sass::Script::Value::ArgList.new(splat.to_a, arg_hash(kwarg_splat), splat.separator)
122
+ end
68
123
  end
69
124
 
70
- yield env
71
- rescue Exception => e
72
- ensure
73
- # If there's a keyword exception, we don't want to throw it immediately,
74
- # because the invalid keywords may be part of a glob argument that should be
75
- # passed on to another function. So we only raise it if we reach the end of
76
- # this function *and* the keywords attached to the argument list glob object
77
- # haven't been accessed.
78
- #
79
- # The keyword exception takes precedence over any Sass errors, but not over
80
- # non-Sass exceptions.
81
- if keyword_exception &&
82
- !(arg_list && arg_list.keywords_accessed) &&
83
- (e.nil? || e.is_a?(Sass::SyntaxError))
84
- raise keyword_exception
85
- elsif e
86
- raise e
125
+ private
126
+
127
+ def arg_hash(map)
128
+ Sass::Util.map_keys(map.to_h) do |key|
129
+ next key.value if key.is_a?(Sass::Script::Value::String)
130
+ raise Sass::SyntaxError.new("Variable keyword argument map must have string keys.\n" +
131
+ "#{key.inspect} is not a string in #{map.inspect}.");
132
+ end
87
133
  end
88
134
  end
89
135
 
@@ -267,7 +313,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
267
313
 
268
314
  args = node.args.map {|a| a.perform(@environment)}
269
315
  keywords = Sass::Util.map_hash(node.keywords) {|k, v| [k, v.perform(@environment)]}
270
- splat = node.splat.perform(@environment) if node.splat
316
+ splat = self.class.perform_splat(node.splat, node.kwarg_splat, @environment)
271
317
 
272
318
  self.class.perform_arguments(mixin, args, keywords, splat) do |env|
273
319
  env.caller = Sass::Environment.new(@environment)
@@ -1621,6 +1621,26 @@ SASS
1621
1621
  SCSS
1622
1622
  end
1623
1623
 
1624
+ def test_mixin_var_kwargs
1625
+ assert_scss_to_sass <<SASS, <<SCSS
1626
+ =foo($a: b, $c: d)
1627
+ a: $a
1628
+ c: $c
1629
+
1630
+ .foo
1631
+ +foo($list..., $map...)
1632
+ SASS
1633
+ @mixin foo($a: b, $c: d) {
1634
+ a: $a;
1635
+ c: $c;
1636
+ }
1637
+
1638
+ .foo {
1639
+ @include foo($list..., $map...);
1640
+ }
1641
+ SCSS
1642
+ end
1643
+
1624
1644
  def test_function_var_args
1625
1645
  assert_scss_to_sass <<SASS, <<SCSS
1626
1646
  @function foo($args...)
@@ -1648,6 +1668,24 @@ SASS
1648
1668
  SCSS
1649
1669
  end
1650
1670
 
1671
+ def test_function_var_kwargs
1672
+ assert_scss_to_sass <<SASS, <<SCSS
1673
+ @function foo($a: b, $c: d)
1674
+ @return foo
1675
+
1676
+ .foo
1677
+ a: foo($list..., $map...)
1678
+ SASS
1679
+ @function foo($a: b, $c: d) {
1680
+ @return foo;
1681
+ }
1682
+
1683
+ .foo {
1684
+ a: foo($list..., $map...);
1685
+ }
1686
+ SCSS
1687
+ end
1688
+
1651
1689
  def test_at_root
1652
1690
  assert_scss_to_sass <<SASS, <<SCSS
1653
1691
  .foo
@@ -1684,6 +1722,24 @@ SASS
1684
1722
  SCSS
1685
1723
  end
1686
1724
 
1725
+ def test_function_var_kwargs_with_list
1726
+ assert_scss_to_sass <<SASS, <<SCSS
1727
+ @function foo($a: b, $c: d)
1728
+ @return $a, $c
1729
+
1730
+ .foo
1731
+ a: foo($list..., $map...)
1732
+ SASS
1733
+ @function foo($a: b, $c: d) {
1734
+ @return $a, $c;
1735
+ }
1736
+
1737
+ .foo {
1738
+ a: foo($list..., $map...);
1739
+ }
1740
+ SCSS
1741
+ end
1742
+
1687
1743
  ## Regression Tests
1688
1744
 
1689
1745
  def test_list_in_args
@@ -3282,6 +3282,26 @@ SASS
3282
3282
  ], e.sass_backtrace)
3283
3283
  end
3284
3284
 
3285
+ def test_mixin_with_args_and_varargs_passed_no_var_args
3286
+ assert_equal <<CSS, render(<<SASS, :syntax => :scss)
3287
+ .foo {
3288
+ a: 1;
3289
+ b: 2;
3290
+ c: 3; }
3291
+ CSS
3292
+ @mixin three-or-more-args($a, $b, $c, $rest...) {
3293
+ a: $a;
3294
+ b: $b;
3295
+ c: $c;
3296
+ }
3297
+
3298
+ .foo {
3299
+ @include three-or-more-args($a: 1, $b: 2, $c: 3);
3300
+ }
3301
+ SASS
3302
+
3303
+ end
3304
+
3285
3305
  private
3286
3306
 
3287
3307
  def assert_hash_has(hash, expected)
@@ -1350,6 +1350,12 @@ WARNING
1350
1350
  end
1351
1351
  end
1352
1352
 
1353
+ def test_keywords
1354
+ # The actual functionality is tested in tests where real arglists are passed.
1355
+ assert_error_message("12 is not a variable argument list for `keywords'", "keywords(12)")
1356
+ assert_error_message("(1 2 3) is not a variable argument list for `keywords'", "keywords(1 2 3)")
1357
+ end
1358
+
1353
1359
  def test_partial_list_of_pairs_doesnt_work_as_a_map
1354
1360
  assert_raises(Sass::SyntaxError) {evaluate("map-get((foo bar, baz bang, bip), 1)")}
1355
1361
  assert_raises(Sass::SyntaxError) {evaluate("map-get((foo bar, baz bang, bip bap bop), 1)")}
@@ -954,6 +954,139 @@ CSS
954
954
  SCSS
955
955
  end
956
956
 
957
+ def test_mixin_var_keyword_args
958
+ assert_equal <<CSS, render(<<SCSS)
959
+ .foo {
960
+ a: 1;
961
+ b: 2;
962
+ c: 3; }
963
+ CSS
964
+ @mixin foo($args...) {
965
+ a: map-get(keywords($args), a);
966
+ b: map-get(keywords($args), b);
967
+ c: map-get(keywords($args), c);
968
+ }
969
+
970
+ .foo {@include foo($a: 1, $b: 2, $c: 3)}
971
+ SCSS
972
+ end
973
+
974
+ def test_mixin_empty_var_keyword_args
975
+ assert_equal <<CSS, render(<<SCSS)
976
+ .foo {
977
+ length: 0; }
978
+ CSS
979
+ @mixin foo($args...) {
980
+ length: length(keywords($args));
981
+ }
982
+
983
+ .foo {@include foo}
984
+ SCSS
985
+ end
986
+
987
+ def test_mixin_map_splat
988
+ assert_equal <<CSS, render(<<SCSS)
989
+ .foo {
990
+ a: 1;
991
+ b: 2;
992
+ c: 3; }
993
+ CSS
994
+ @mixin foo($a, $b, $c) {
995
+ a: $a;
996
+ b: $b;
997
+ c: $c;
998
+ }
999
+
1000
+ .foo {
1001
+ $map: (a: 1, b: 2, c: 3);
1002
+ @include foo($map...);
1003
+ }
1004
+ SCSS
1005
+ end
1006
+
1007
+ def test_mixin_map_and_list_splat
1008
+ assert_equal <<CSS, render(<<SCSS)
1009
+ .foo {
1010
+ a: x;
1011
+ b: y;
1012
+ c: z;
1013
+ d: 1;
1014
+ e: 2;
1015
+ f: 3; }
1016
+ CSS
1017
+ @mixin foo($a, $b, $c, $d, $e, $f) {
1018
+ a: $a;
1019
+ b: $b;
1020
+ c: $c;
1021
+ d: $d;
1022
+ e: $e;
1023
+ f: $f;
1024
+ }
1025
+
1026
+ .foo {
1027
+ $list: x y z;
1028
+ $map: (d: 1, e: 2, f: 3);
1029
+ @include foo($list..., $map...);
1030
+ }
1031
+ SCSS
1032
+ end
1033
+
1034
+ def test_mixin_map_splat_takes_precedence_over_pass_through
1035
+ assert_equal <<CSS, render(<<SCSS)
1036
+ .foo {
1037
+ a: 1;
1038
+ b: 2;
1039
+ c: z; }
1040
+ CSS
1041
+ @mixin foo($args...) {
1042
+ $map: (c: z);
1043
+ @include bar($args..., $map...);
1044
+ }
1045
+
1046
+ @mixin bar($a, $b, $c) {
1047
+ a: $a;
1048
+ b: $b;
1049
+ c: $c;
1050
+ }
1051
+
1052
+ .foo {
1053
+ @include foo(1, $b: 2, $c: 3);
1054
+ }
1055
+ SCSS
1056
+ end
1057
+
1058
+ def test_mixin_list_of_pairs_splat_treated_as_list
1059
+ assert_equal <<CSS, render(<<SCSS)
1060
+ .foo {
1061
+ a: a 1;
1062
+ b: b 2;
1063
+ c: c 3; }
1064
+ CSS
1065
+ @mixin foo($a, $b, $c) {
1066
+ a: $a;
1067
+ b: $b;
1068
+ c: $c;
1069
+ }
1070
+
1071
+ .foo {
1072
+ @include foo((a 1, b 2, c 3)...);
1073
+ }
1074
+ SCSS
1075
+ end
1076
+
1077
+ def test_mixin_keyword_splat_must_have_string_keys
1078
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
1079
+ Variable keyword argument map must have string keys.
1080
+ 12 is not a string in (12: 1).
1081
+ MESSAGE
1082
+ @mixin foo($a) {
1083
+ a: $a;
1084
+ }
1085
+
1086
+ .foo {@include foo((12: 1)...)}
1087
+ SCSS
1088
+ end
1089
+
957
1090
  def test_mixin_var_args_with_keyword
958
1091
  assert_raise_message(Sass::SyntaxError, "Positional arguments must come before keyword arguments.") {render <<SCSS}
959
1092
  @mixin foo($a, $b...) {
@@ -987,6 +1120,46 @@ SCSS
987
1120
  SCSS
988
1121
  end
989
1122
 
1123
+ def test_mixin_map_splat_before_list_splat
1124
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was (2 3)).") {render <<SCSS}
1125
+ @mixin foo($a, $b, $c) {
1126
+ a: $a;
1127
+ b: $b;
1128
+ c: $c;
1129
+ }
1130
+
1131
+ .foo {
1132
+ @include foo((a: 1)..., (2 3)...);
1133
+ }
1134
+ SCSS
1135
+ end
1136
+
1137
+ def test_mixin_map_splat_with_unknown_keyword
1138
+ assert_raise_message(Sass::SyntaxError, "Mixin foo doesn't have an argument named $c.") {render <<SCSS}
1139
+ @mixin foo($a, $b) {
1140
+ a: $a;
1141
+ b: $b;
1142
+ }
1143
+
1144
+ .foo {
1145
+ @include foo(1, 2, (c: 1)...);
1146
+ }
1147
+ SCSS
1148
+ end
1149
+
1150
+ def test_mixin_map_splat_with_wrong_type
1151
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was 12).") {render <<SCSS}
1152
+ @mixin foo($a, $b) {
1153
+ a: $a;
1154
+ b: $b;
1155
+ }
1156
+
1157
+ .foo {
1158
+ @include foo((1, 2)..., 12...);
1159
+ }
1160
+ SCSS
1161
+ end
1162
+
990
1163
  def test_function_var_args
991
1164
  assert_equal <<CSS, render(<<SCSS)
992
1165
  .foo {
@@ -1112,10 +1285,122 @@ CSS
1112
1285
  SCSS
1113
1286
  end
1114
1287
 
1288
+ def test_function_var_keyword_args
1289
+ assert_equal <<CSS, render(<<SCSS)
1290
+ .foo {
1291
+ val: "a: 1, b: 2, c: 3"; }
1292
+ CSS
1293
+ @function foo($args...) {
1294
+ @return "a: \#{map-get(keywords($args), a)}, " +
1295
+ "b: \#{map-get(keywords($args), b)}, " +
1296
+ "c: \#{map-get(keywords($args), c)}";
1297
+ }
1298
+
1299
+ .foo {val: foo($a: 1, $b: 2, $c: 3)}
1300
+ SCSS
1301
+ end
1302
+
1303
+ def test_function_empty_var_keyword_args
1304
+ assert_equal <<CSS, render(<<SCSS)
1305
+ .foo {
1306
+ length: 0; }
1307
+ CSS
1308
+ @function foo($args...) {
1309
+ @return length(keywords($args));
1310
+ }
1311
+
1312
+ .foo {length: foo()}
1313
+ SCSS
1314
+ end
1315
+
1316
+ def test_function_map_splat
1317
+ assert_equal <<CSS, render(<<SCSS)
1318
+ .foo {
1319
+ val: "a: 1, b: 2, c: 3"; }
1320
+ CSS
1321
+ @function foo($a, $b, $c) {
1322
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
1323
+ }
1324
+
1325
+ .foo {
1326
+ $map: (a: 1, b: 2, c: 3);
1327
+ val: foo($map...);
1328
+ }
1329
+ SCSS
1330
+ end
1331
+
1332
+ def test_function_map_and_list_splat
1333
+ assert_equal <<CSS, render(<<SCSS)
1334
+ .foo {
1335
+ val: "a: x, b: y, c: z, d: 1, e: 2, f: 3"; }
1336
+ CSS
1337
+ @function foo($a, $b, $c, $d, $e, $f) {
1338
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}, d: \#{$d}, e: \#{$e}, f: \#{$f}";
1339
+ }
1340
+
1341
+ .foo {
1342
+ $list: x y z;
1343
+ $map: (d: 1, e: 2, f: 3);
1344
+ val: foo($list..., $map...);
1345
+ }
1346
+ SCSS
1347
+ end
1348
+
1349
+ def test_function_map_splat_takes_precedence_over_pass_through
1350
+ assert_equal <<CSS, render(<<SCSS)
1351
+ .foo {
1352
+ val: "a: 1, b: 2, c: z"; }
1353
+ CSS
1354
+ @function foo($args...) {
1355
+ $map: (c: z);
1356
+ @return bar($args..., $map...);
1357
+ }
1358
+
1359
+ @function bar($a, $b, $c) {
1360
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
1361
+ }
1362
+
1363
+ .foo {
1364
+ val: foo(1, $b: 2, $c: 3);
1365
+ }
1366
+ SCSS
1367
+ end
1368
+
1369
+ def test_ruby_function_map_splat_takes_precedence_over_pass_through
1370
+ assert_equal <<CSS, render(<<SCSS)
1371
+ .foo {
1372
+ val: 1 2 3 z; }
1373
+ CSS
1374
+ @function foo($args...) {
1375
+ $map: (val: z);
1376
+ @return append($args..., $map...);
1377
+ }
1378
+
1379
+ .foo {
1380
+ val: foo(1 2 3, $val: 4)
1381
+ }
1382
+ SCSS
1383
+ end
1384
+
1385
+ def test_function_list_of_pairs_splat_treated_as_list
1386
+ assert_equal <<CSS, render(<<SCSS)
1387
+ .foo {
1388
+ val: "a: a 1, b: b 2, c: c 3"; }
1389
+ CSS
1390
+ @function foo($a, $b, $c) {
1391
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
1392
+ }
1393
+
1394
+ .foo {
1395
+ val: foo((a 1, b 2, c 3)...);
1396
+ }
1397
+ SCSS
1398
+ end
1399
+
1115
1400
  def test_function_var_args_with_keyword
1116
1401
  assert_raise_message(Sass::SyntaxError, "Positional arguments must come before keyword arguments.") {render <<SCSS}
1117
1402
  @function foo($a, $b...) {
1118
- @return "a: \#{$a}, b: $b";
1403
+ @return "a: \#{$a}, b: \#{$b}";
1119
1404
  }
1120
1405
 
1121
1406
  .foo {val: foo($a: 1, 2, 3, 4)}
@@ -1135,7 +1420,7 @@ SCSS
1135
1420
  def test_function_keyword_for_unknown_arg_with_var_args
1136
1421
  assert_raise_message(Sass::SyntaxError, "Function foo doesn't have an argument named $c.") {render <<SCSS}
1137
1422
  @function foo($a, $b...) {
1138
- @return "a: \#{$a}, b: \#{$b}";
1423
+ @return "a: \#{$a}, b: \#{length($b)}";
1139
1424
  }
1140
1425
 
1141
1426
  .foo {val: foo(1, $c: 2 3 4)}
@@ -1155,6 +1440,55 @@ CSS
1155
1440
  SCSS
1156
1441
  end
1157
1442
 
1443
+ def test_function_map_splat_before_list_splat
1444
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was (2 3)).") {render <<SCSS}
1445
+ @function foo($a, $b, $c) {
1446
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
1447
+ }
1448
+
1449
+ .foo {
1450
+ val: foo((a: 1)..., (2 3)...);
1451
+ }
1452
+ SCSS
1453
+ end
1454
+
1455
+ def test_function_map_splat_with_unknown_keyword
1456
+ assert_raise_message(Sass::SyntaxError, "Function foo doesn't have an argument named $c.") {render <<SCSS}
1457
+ @function foo($a, $b) {
1458
+ @return "a: \#{$a}, b: \#{$b}";
1459
+ }
1460
+
1461
+ .foo {
1462
+ val: foo(1, 2, (c: 1)...);
1463
+ }
1464
+ SCSS
1465
+ end
1466
+
1467
+ def test_function_map_splat_with_wrong_type
1468
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was 12).") {render <<SCSS}
1469
+ @function foo($a, $b) {
1470
+ @return "a: \#{$a}, b: \#{$b}";
1471
+ }
1472
+
1473
+ .foo {
1474
+ val: foo((1, 2)..., 12...);
1475
+ }
1476
+ SCSS
1477
+ end
1478
+
1479
+ def test_function_keyword_splat_must_have_string_keys
1480
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
1481
+ Variable keyword argument map must have string keys.
1482
+ 12 is not a string in (12: 1).
1483
+ MESSAGE
1484
+ @function foo($a) {
1485
+ @return $a;
1486
+ }
1487
+
1488
+ .foo {val: foo((12: 1)...)}
1489
+ SCSS
1490
+ end
1491
+
1158
1492
  ## Interpolation
1159
1493
 
1160
1494
  def test_basic_selector_interpolation
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass
3
3
  version: !ruby/object:Gem::Version
4
- hash: 592302839
4
+ hash: 592302835
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 3
8
8
  - 3
9
9
  - 0
10
10
  - alpha
11
- - 253
12
- version: 3.3.0.alpha.253
11
+ - 255
12
+ version: 3.3.0.alpha.255
13
13
  platform: ruby
14
14
  authors:
15
15
  - Nathan Weizenbaum
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2013-09-06 00:00:00 -04:00
22
+ date: 2013-09-18 00:00:00 -04:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
@@ -129,10 +129,10 @@ files:
129
129
  - lib/sass/script/tree/map_literal.rb
130
130
  - lib/sass/script/tree/node.rb
131
131
  - lib/sass/script/tree/operation.rb
132
+ - lib/sass/script/tree/selector.rb
132
133
  - lib/sass/script/tree/string_interpolation.rb
133
134
  - lib/sass/script/tree/unary_operation.rb
134
135
  - lib/sass/script/tree/variable.rb
135
- - lib/sass/script/tree/selector.rb
136
136
  - lib/sass/script/value.rb
137
137
  - lib/sass/script/value/arg_list.rb
138
138
  - lib/sass/script/value/base.rb
@@ -159,18 +159,18 @@ files:
159
159
  - lib/sass/shared.rb
160
160
  - lib/sass/supports.rb
161
161
  - lib/sass/version.rb
162
- - lib/sass/tree/charset_node.rb
163
162
  - lib/sass/tree/comment_node.rb
163
+ - lib/sass/tree/charset_node.rb
164
+ - lib/sass/tree/debug_node.rb
164
165
  - lib/sass/tree/content_node.rb
165
166
  - lib/sass/tree/css_import_node.rb
166
- - lib/sass/tree/debug_node.rb
167
167
  - lib/sass/tree/directive_node.rb
168
168
  - lib/sass/tree/each_node.rb
169
169
  - lib/sass/tree/extend_node.rb
170
170
  - lib/sass/tree/for_node.rb
171
171
  - lib/sass/tree/function_node.rb
172
- - lib/sass/tree/if_node.rb
173
172
  - lib/sass/tree/import_node.rb
173
+ - lib/sass/tree/if_node.rb
174
174
  - lib/sass/tree/media_node.rb
175
175
  - lib/sass/tree/mixin_def_node.rb
176
176
  - lib/sass/tree/mixin_node.rb
@@ -179,9 +179,10 @@ files:
179
179
  - lib/sass/tree/return_node.rb
180
180
  - lib/sass/tree/root_node.rb
181
181
  - lib/sass/tree/rule_node.rb
182
- - lib/sass/tree/supports_node.rb
183
182
  - lib/sass/tree/trace_node.rb
183
+ - lib/sass/tree/supports_node.rb
184
184
  - lib/sass/tree/variable_node.rb
185
+ - lib/sass/tree/warn_node.rb
185
186
  - lib/sass/tree/visitors/base.rb
186
187
  - lib/sass/tree/visitors/check_nesting.rb
187
188
  - lib/sass/tree/visitors/convert.rb
@@ -191,7 +192,6 @@ files:
191
192
  - lib/sass/tree/visitors/perform.rb
192
193
  - lib/sass/tree/visitors/set_options.rb
193
194
  - lib/sass/tree/visitors/to_css.rb
194
- - lib/sass/tree/warn_node.rb
195
195
  - lib/sass/tree/while_node.rb
196
196
  - lib/sass/tree/at_root_node.rb
197
197
  - lib/sass/source/map.rb