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

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