sass 3.2.0.alpha.278 → 3.2.0.alpha.291

Sign up to get free protection for your applications and to get access to all the features.
@@ -170,16 +170,16 @@ module Sass
170
170
 
171
171
  def mixin_directive
172
172
  name = tok! IDENT
173
- args = sass_script(:parse_mixin_definition_arglist)
173
+ args, splat = sass_script(:parse_mixin_definition_arglist)
174
174
  ss
175
- block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
175
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat)), :directive)
176
176
  end
177
177
 
178
178
  def include_directive
179
179
  name = tok! IDENT
180
- args, keywords = sass_script(:parse_mixin_include_arglist)
180
+ args, keywords, splat = sass_script(:parse_mixin_include_arglist)
181
181
  ss
182
- include_node = node(Sass::Tree::MixinNode.new(name, args, keywords))
182
+ include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat))
183
183
  if tok?(/\{/)
184
184
  include_node.has_children = true
185
185
  block(include_node, :directive)
@@ -195,9 +195,9 @@ module Sass
195
195
 
196
196
  def function_directive
197
197
  name = tok! IDENT
198
- args = sass_script(:parse_function_definition_arglist)
198
+ args, splat = sass_script(:parse_function_definition_arglist)
199
199
  ss
200
- block(node(Sass::Tree::FunctionNode.new(name, args)), :function)
200
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat)), :function)
201
201
  end
202
202
 
203
203
  def return_directive
@@ -15,11 +15,18 @@ module Sass
15
15
  # @return [Array<Script::Node>]
16
16
  attr_accessor :args
17
17
 
18
+ # The splat argument for this function, if one exists.
19
+ #
20
+ # @return [Script::Node?]
21
+ attr_accessor :splat
22
+
18
23
  # @param name [String] The function name
19
24
  # @param args [Array<(Script::Node, Script::Node)>] The arguments for the function.
20
- def initialize(name, args)
25
+ # @param splat [Script::Node] See \{#splat}
26
+ def initialize(name, args, splat)
21
27
  @name = name
22
28
  @args = args
29
+ @splat = splat
23
30
  super()
24
31
  end
25
32
  end
@@ -15,15 +15,22 @@ module Sass
15
15
  # @return [Array<(Script::Node, Script::Node)>]
16
16
  attr_accessor :args
17
17
 
18
+ # The splat argument for this mixin, if one exists.
19
+ #
20
+ # @return [Script::Node?]
21
+ attr_accessor :splat
22
+
18
23
  # Whether the mixin uses `@content`. Set during the nesting check phase.
19
24
  # @return [Boolean]
20
25
  attr_accessor :has_content
21
26
 
22
27
  # @param name [String] The mixin name
23
28
  # @param args [Array<(Script::Node, Script::Node)>] See \{#args}
24
- def initialize(name, args)
29
+ # @param splat [Script::Node] See \{#splat}
30
+ def initialize(name, args, splat)
25
31
  @name = name
26
32
  @args = args
33
+ @splat = splat
27
34
  super()
28
35
  end
29
36
  end
@@ -19,13 +19,20 @@ module Sass::Tree
19
19
  # @return [{String => Script::Node}]
20
20
  attr_accessor :keywords
21
21
 
22
+ # The splat argument for this mixin, if one exists.
23
+ #
24
+ # @return [Script::Node?]
25
+ attr_accessor :splat
26
+
22
27
  # @param name [String] The name of the mixin
23
28
  # @param args [Array<Script::Node>] See \{#args}
29
+ # @param splat [Script::Node] See \{#splat}
24
30
  # @param keywords [{String => Script::Node}] See \{#keywords}
25
- def initialize(name, args, keywords)
31
+ def initialize(name, args, keywords, splat)
26
32
  @name = name
27
33
  @args = args
28
34
  @keywords = keywords
35
+ @splat = splat
29
36
  super()
30
37
  end
31
38
  end
@@ -121,6 +121,10 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
121
121
  args = node.args.map do |v, d|
122
122
  d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
123
123
  end.join(", ")
124
+ if node.splat
125
+ args << ", " unless node.args.empty?
126
+ args << node.splat.to_sass(@options) << "..."
127
+ end
124
128
 
125
129
  "#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"
126
130
  end
@@ -167,27 +171,39 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
167
171
 
168
172
  def visit_mixindef(node)
169
173
  args =
170
- if node.args.empty?
174
+ if node.args.empty? && node.splat.nil?
171
175
  ""
172
176
  else
173
- '(' + node.args.map do |v, d|
177
+ str = '('
178
+ str << node.args.map do |v, d|
174
179
  if d
175
180
  "#{v.to_sass(@options)}: #{d.to_sass(@options)}"
176
181
  else
177
182
  v.to_sass(@options)
178
183
  end
179
- end.join(", ") + ')'
184
+ end.join(", ")
185
+
186
+ if node.splat
187
+ str << ", " unless node.args.empty?
188
+ str << node.splat.to_sass(@options) << '...'
189
+ end
190
+
191
+ str << ')'
180
192
  end
181
-
193
+
182
194
  "#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
183
195
  end
184
196
 
185
197
  def visit_mixin(node)
186
- unless node.args.empty? && node.keywords.empty?
198
+ unless node.args.empty? && node.keywords.empty? && node.splat.nil?
187
199
  args = node.args.map {|a| a.to_sass(@options)}.join(", ")
188
200
  keywords = Sass::Util.hash_to_a(node.keywords).
189
201
  map {|k, v| "$#{dasherize(k)}: #{v.to_sass(@options)}"}.join(', ')
190
- arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
202
+ if node.splat
203
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
204
+ splat = "#{splat}#{node.splat.to_sass(@options)}..."
205
+ end
206
+ arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
191
207
  end
192
208
  "#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n"
193
209
  end
@@ -7,6 +7,83 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
7
7
  new(environment).send(:visit, root)
8
8
  end
9
9
 
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}"
14
+
15
+ begin
16
+ unless keywords.empty?
17
+ unknown_args = keywords.keys - callable.args.map {|var| var.first.underscored_name}
18
+ if callable.splat && unknown_args.include?(callable.splat.underscored_name)
19
+ raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} cannot be used as a named argument.")
20
+ elsif unknown_args.any?
21
+ raise Sass::SyntaxError.new("#{desc} doesn't have #{unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'} #{unknown_args.map{|name| "$#{name}"}.join ', '}.")
22
+ end
23
+ end
24
+ rescue Sass::SyntaxError => keyword_exception
25
+ end
26
+
27
+ # If there's no splat, raise the keyword exception immediately. The actual
28
+ # raising happens in the ensure clause at the end of this function.
29
+ return if keyword_exception && !callable.splat
30
+
31
+ if args.size > callable.args.size && !callable.splat
32
+ takes = callable.args.size
33
+ passed = args.size
34
+ raise Sass::SyntaxError.new(
35
+ "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
36
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
37
+ end
38
+
39
+ splat_sep = :comma
40
+ if splat
41
+ args += splat.to_a
42
+ splat_sep = splat.separator if splat.is_a?(Sass::Script::List)
43
+ # If the splat argument exists, there won't be any keywords passed in
44
+ # manually, so we can safely overwrite rather than merge here.
45
+ keywords = splat.keywords if splat.is_a?(Sass::Script::ArgList)
46
+ end
47
+
48
+ keywords = keywords.dup
49
+ env = Sass::Environment.new(callable.environment)
50
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
51
+ if value && keywords.include?(var.underscored_name)
52
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} both by position and by name.")
53
+ end
54
+
55
+ value ||= keywords.delete(var.underscored_name)
56
+ value ||= default && default.perform(env)
57
+ raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
58
+ env.set_local_var(var.name, value)
59
+ end
60
+
61
+ if callable.splat
62
+ rest = args[callable.args.length..-1]
63
+ arg_list = Sass::Script::ArgList.new(rest, keywords.dup, splat_sep)
64
+ arg_list.options = env.options
65
+ env.set_local_var(callable.splat.name, arg_list)
66
+ end
67
+
68
+ yield env
69
+ ensure
70
+ # If there's a keyword exception, we don't want to throw it immediately,
71
+ # because the invalid keywords may be part of a glob argument that should be
72
+ # passed on to another function. So we only raise it if we reach the end of
73
+ # this function *and* the keywords attached to the argument list glob object
74
+ # haven't been accessed.
75
+ #
76
+ # The keyword exception takes precedence over any Sass errors, but not over
77
+ # non-Sass exceptions.
78
+ if keyword_exception &&
79
+ !(arg_list && arg_list.keywords_accessed) &&
80
+ ($!.nil? || $!.is_a?(Sass::SyntaxError))
81
+ raise keyword_exception
82
+ elsif $!
83
+ raise $!
84
+ end
85
+ end
86
+
10
87
  protected
11
88
 
12
89
  def initialize(env)
@@ -114,7 +191,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
114
191
  def visit_function(node)
115
192
  env = Sass::Environment.new(@environment, node.options)
116
193
  @environment.set_local_function(node.name,
117
- Sass::Callable.new(node.name, node.args, env, node.children, !:has_content))
194
+ Sass::Callable.new(node.name, node.args, node.splat, env, node.children, !:has_content, "function"))
118
195
  []
119
196
  end
120
197
 
@@ -157,7 +234,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
157
234
  def visit_mixindef(node)
158
235
  env = Sass::Environment.new(@environment, node.options)
159
236
  @environment.set_local_mixin(node.name,
160
- Sass::Callable.new(node.name, node.args, env, node.children, node.has_content))
237
+ Sass::Callable.new(node.name, node.args, node.splat, env, node.children, node.has_content, "mixin"))
161
238
  []
162
239
  end
163
240
 
@@ -174,40 +251,18 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
174
251
  raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
175
252
  end
176
253
 
177
- passed_args = node.args.dup
178
- passed_keywords = node.keywords.dup
254
+ args = node.args.map {|a| a.perform(@environment)}
255
+ keywords = Sass::Util.map_hash(node.keywords) {|k, v| [k, v.perform(@environment)]}
256
+ splat = node.splat.perform(@environment) if node.splat
179
257
 
180
- raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < passed_args.size
181
- Mixin #{node.name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
182
- but #{node.args.size} #{node.args.size == 1 ? 'was' : 'were'} passed.
183
- END
258
+ self.class.perform_arguments(mixin, args, keywords, splat) do |env|
259
+ env.caller = Sass::Environment.new(@environment)
260
+ env.content = node.children if node.has_children
184
261
 
185
- passed_keywords.each do |name, value|
186
- # TODO: Make this fast
187
- unless mixin.args.find {|(var, default)| var.underscored_name == name}
188
- raise Sass::SyntaxError.new("Mixin #{node.name} doesn't have an argument named $#{name}")
189
- end
190
- end
191
-
192
- environment = mixin.args.zip(passed_args).
193
- inject(Sass::Environment.new(mixin.environment)) do |env, ((var, default), value)|
194
- env.set_local_var(var.name,
195
- if value
196
- value.perform(@environment)
197
- elsif kv = passed_keywords[var.underscored_name]
198
- kv.perform(@environment)
199
- elsif default
200
- default.perform(env)
201
- end)
202
- raise Sass::SyntaxError.new("Mixin #{node.name} is missing argument #{var.inspect}.") unless env.var(var.name)
203
- env
262
+ trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
263
+ with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
264
+ trace_node
204
265
  end
205
- environment.caller = Sass::Environment.new(@environment)
206
- environment.content = node.children if node.has_children
207
-
208
- trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
209
- with_environment(environment) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
210
- trace_node
211
266
  rescue Sass::SyntaxError => e
212
267
  unless include_loop
213
268
  e.modify_backtrace(:mixin => node.name, :line => node.line)
@@ -1562,6 +1562,60 @@ foo {
1562
1562
  SCSS
1563
1563
  end
1564
1564
 
1565
+ def test_mixin_var_args
1566
+ assert_scss_to_sass <<SASS, <<SCSS
1567
+ =foo($args...)
1568
+ a: b
1569
+
1570
+ =bar($a, $args...)
1571
+ a: b
1572
+
1573
+ .foo
1574
+ +foo($list...)
1575
+ +bar(1, $list...)
1576
+ SASS
1577
+ @mixin foo($args...) {
1578
+ a: b;
1579
+ }
1580
+
1581
+ @mixin bar($a, $args...) {
1582
+ a: b;
1583
+ }
1584
+
1585
+ .foo {
1586
+ @include foo($list...);
1587
+ @include bar(1, $list...);
1588
+ }
1589
+ SCSS
1590
+ end
1591
+
1592
+ def test_function_var_args
1593
+ assert_scss_to_sass <<SASS, <<SCSS
1594
+ @function foo($args...)
1595
+ @return foo
1596
+
1597
+ @function bar($a, $args...)
1598
+ @return bar
1599
+
1600
+ .foo
1601
+ a: foo($list...)
1602
+ b: bar(1, $list...)
1603
+ SASS
1604
+ @function foo($args...) {
1605
+ @return foo;
1606
+ }
1607
+
1608
+ @function bar($a, $args...) {
1609
+ @return bar;
1610
+ }
1611
+
1612
+ .foo {
1613
+ a: foo($list...);
1614
+ b: bar(1, $list...);
1615
+ }
1616
+ SCSS
1617
+ end
1618
+
1565
1619
  ## Regression Tests
1566
1620
 
1567
1621
  def test_media_query_with_expr
@@ -93,7 +93,7 @@ MSG
93
93
  "a-\#{$b\n c: d" => ['Invalid CSS after "a-#{$b": expected "}", was ""', 1],
94
94
  "=a($b: 1, $c)" => "Required argument $c must come before any optional arguments.",
95
95
  "=a($b: 1)\n a: $b\ndiv\n +a(1,2)" => "Mixin a takes 1 argument but 2 were passed.",
96
- "=a($b: 1)\n a: $b\ndiv\n +a(1,$c: 3)" => "Mixin a doesn't have an argument named $c",
96
+ "=a($b: 1)\n a: $b\ndiv\n +a(1,$c: 3)" => "Mixin a doesn't have an argument named $c.",
97
97
  "=a($b)\n a: $b\ndiv\n +a" => "Mixin a is missing argument $b.",
98
98
  "@function foo()\n 1 + 2" => "Functions can only contain variable declarations and control directives.",
99
99
  "@function foo()\n foo: bar" => "Functions can only contain variable declarations and control directives.",
@@ -105,10 +105,10 @@ MSG
105
105
  "@function foo($)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "$)"', 1],
106
106
  "@function foo()\n @return" => 'Invalid @return: expected expression.',
107
107
  "@function foo()\n @return 1\n $var: val" => 'Illegal nesting: Nothing may be nested beneath return directives.',
108
- "@function foo($a)\n @return 1\na\n b: foo()" => 'Function foo is missing argument $a',
109
- "@function foo()\n @return 1\na\n b: foo(2)" => 'Wrong number of arguments (1 for 0) for `foo\'',
110
- "@function foo()\n @return 1\na\n b: foo($a: 1)" => "Function foo doesn't have an argument named $a",
111
- "@function foo()\n @return 1\na\n b: foo($a: 1, $b: 2)" => "Function foo doesn't have the following arguments: $a, $b",
108
+ "@function foo($a)\n @return 1\na\n b: foo()" => 'Function foo is missing argument $a.',
109
+ "@function foo()\n @return 1\na\n b: foo(2)" => 'Function foo takes 0 arguments but 1 was passed.',
110
+ "@function foo()\n @return 1\na\n b: foo($a: 1)" => "Function foo doesn't have an argument named $a.",
111
+ "@function foo()\n @return 1\na\n b: foo($a: 1, $b: 2)" => "Function foo doesn't have the following arguments: $a, $b.",
112
112
  "@return 1" => '@return may only be used within a function.',
113
113
  "@if true\n @return 1" => '@return may only be used within a function.',
114
114
  "@mixin foo\n @return 1\n@include foo" => ['@return may only be used within a function.', 2],
@@ -122,7 +122,7 @@ MSG
122
122
  '@for $a from "foo" to 1' => '"foo" is not an integer.',
123
123
  '@for $a from 1 to "2"' => '"2" is not an integer.',
124
124
  '@for $a from 1 to "foo"' => '"foo" is not an integer.',
125
- '@for $a from 1 to 1.232323' => '1.232 is not an integer.',
125
+ '@for $a from 1 to 1.232323' => '1.23232 is not an integer.',
126
126
  '@for $a from 1px to 3em' => "Incompatible units: 'em' and 'px'.",
127
127
  '@if' => "Invalid if directive '@if': expected expression.",
128
128
  '@while' => "Invalid while directive '@while': expected expression.",
@@ -1294,7 +1294,7 @@ bar
1294
1294
  SASS
1295
1295
  flunk("Expected exception")
1296
1296
  rescue Sass::SyntaxError => e
1297
- assert_equal("Function plus is missing argument $var1", e.message)
1297
+ assert_equal("Function plus is missing argument $var1.", e.message)
1298
1298
  end
1299
1299
 
1300
1300
  def test_function_with_extra_argument
@@ -1307,7 +1307,7 @@ bar
1307
1307
  SASS
1308
1308
  flunk("Expected exception")
1309
1309
  rescue Sass::SyntaxError => e
1310
- assert_equal("Function plus doesn't have an argument named $var3", e.message)
1310
+ assert_equal("Function plus doesn't have an argument named $var3.", e.message)
1311
1311
  end
1312
1312
 
1313
1313
  def test_function_with_positional_and_keyword_argument
@@ -1320,7 +1320,7 @@ bar
1320
1320
  SASS
1321
1321
  flunk("Expected exception")
1322
1322
  rescue Sass::SyntaxError => e
1323
- assert_equal("Function plus was passed argument $var2 both by position and by name", e.message)
1323
+ assert_equal("Function plus was passed argument $var2 both by position and by name.", e.message)
1324
1324
  end
1325
1325
 
1326
1326
  def test_function_with_keyword_before_positional_argument
@@ -1333,7 +1333,7 @@ bar
1333
1333
  SASS
1334
1334
  flunk("Expected exception")
1335
1335
  rescue Sass::SyntaxError => e
1336
- assert_equal("Positional arguments must come before keyword arguments", e.message)
1336
+ assert_equal("Positional arguments must come before keyword arguments.", e.message)
1337
1337
  end
1338
1338
 
1339
1339
  def test_function_with_if
@@ -1759,7 +1759,7 @@ SASS
1759
1759
 
1760
1760
  def test_loud_comment_is_evaluated
1761
1761
  assert_equal <<CSS, render(<<SASS)
1762
- /* Hue: 327.216deg */
1762
+ /* Hue: 327.21649deg */
1763
1763
  CSS
1764
1764
  /*!
1765
1765
  Hue: \#{hue(#f836a0)}
@@ -2872,11 +2872,13 @@ SCSS
2872
2872
  end
2873
2873
 
2874
2874
  def test_deprecated_PRECISION
2875
- assert_warning(<<END) {assert_equal 1000.0, Sass::Script::Number::PRECISION}
2875
+ assert_warning(<<END) {assert_equal 100_000.0, Sass::Script::Number::PRECISION}
2876
2876
  Sass::Script::Number::PRECISION is deprecated and will be removed in a future release. Use Sass::Script::Number.precision_factor instead.
2877
2877
  END
2878
2878
  end
2879
+
2879
2880
  def test_changing_precision
2881
+ old_precision = Sass::Script::Number.precision
2880
2882
  begin
2881
2883
  Sass::Script::Number.precision = 8
2882
2884
  assert_equal <<CSS, render(<<SASS)
@@ -2889,7 +2891,7 @@ div
2889
2891
  too-much: 1.000000001
2890
2892
  SASS
2891
2893
  ensure
2892
- Sass::Script::Number.precision = 3
2894
+ Sass::Script::Number.precision = old_precision
2893
2895
  end
2894
2896
  end
2895
2897