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

Sign up to get free protection for your applications and to get access to all the features.
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