sass 3.1.0.alpha.50 → 3.1.0.alpha.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/EDGE_GEM_VERSION +1 -1
- data/VERSION +1 -1
- data/lib/sass.rb +0 -3
- data/lib/sass/engine.rb +30 -9
- data/lib/sass/environment.rb +5 -2
- data/lib/sass/plugin/compiler.rb +13 -7
- data/lib/sass/script/funcall.rb +30 -0
- data/lib/sass/script/parser.rb +26 -2
- data/lib/sass/scss/parser.rb +14 -2
- data/lib/sass/tree/each_node.rb +0 -14
- data/lib/sass/tree/function_node.rb +27 -0
- data/lib/sass/tree/node.rb +0 -33
- data/lib/sass/tree/prop_node.rb +0 -12
- data/lib/sass/tree/return_node.rb +18 -0
- data/lib/sass/tree/root_node.rb +4 -19
- data/lib/sass/tree/visitors/base.rb +9 -1
- data/lib/sass/tree/visitors/check_nesting.rb +101 -0
- data/lib/sass/tree/visitors/convert.rb +12 -0
- data/lib/sass/tree/visitors/cssize.rb +2 -5
- data/lib/sass/tree/visitors/perform.rb +22 -4
- data/test/sass/conversion_test.rb +36 -0
- data/test/sass/engine_test.rb +102 -4
- data/test/sass/scss/scss_test.rb +32 -0
- metadata +5 -2
data/EDGE_GEM_VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.1.0.alpha.
|
1
|
+
3.1.0.alpha.51
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.1.0.alpha.
|
1
|
+
3.1.0.alpha.51
|
data/lib/sass.rb
CHANGED
data/lib/sass/engine.rb
CHANGED
@@ -12,6 +12,8 @@ require 'sass/tree/media_node'
|
|
12
12
|
require 'sass/tree/variable_node'
|
13
13
|
require 'sass/tree/mixin_def_node'
|
14
14
|
require 'sass/tree/mixin_node'
|
15
|
+
require 'sass/tree/function_node'
|
16
|
+
require 'sass/tree/return_node'
|
15
17
|
require 'sass/tree/extend_node'
|
16
18
|
require 'sass/tree/if_node'
|
17
19
|
require 'sass/tree/while_node'
|
@@ -26,6 +28,7 @@ require 'sass/tree/visitors/perform'
|
|
26
28
|
require 'sass/tree/visitors/cssize'
|
27
29
|
require 'sass/tree/visitors/convert'
|
28
30
|
require 'sass/tree/visitors/to_css'
|
31
|
+
require 'sass/tree/visitors/check_nesting'
|
29
32
|
require 'sass/selector'
|
30
33
|
require 'sass/environment'
|
31
34
|
require 'sass/script'
|
@@ -36,24 +39,24 @@ require 'sass/shared'
|
|
36
39
|
|
37
40
|
module Sass
|
38
41
|
|
39
|
-
# A Sass mixin.
|
42
|
+
# A Sass mixin or function.
|
40
43
|
#
|
41
44
|
# `name`: `String`
|
42
|
-
# : The name of the mixin.
|
45
|
+
# : The name of the mixin/function.
|
43
46
|
#
|
44
47
|
# `args`: `Array<(String, Script::Node)>`
|
45
|
-
# : The arguments for the mixin.
|
48
|
+
# : The arguments for the mixin/function.
|
46
49
|
# Each element is a tuple containing the name of the argument
|
47
50
|
# and the parse tree for the default value of the argument.
|
48
51
|
#
|
49
52
|
# `environment`: {Sass::Environment}
|
50
|
-
# : The environment in which the mixin was defined.
|
51
|
-
# This is captured so that the mixin can have access
|
53
|
+
# : The environment in which the mixin/function was defined.
|
54
|
+
# This is captured so that the mixin/function can have access
|
52
55
|
# to local variables defined in its scope.
|
53
56
|
#
|
54
|
-
# `tree`:
|
55
|
-
# : The parse tree for the mixin.
|
56
|
-
|
57
|
+
# `tree`: `Array<Tree::Node>`
|
58
|
+
# : The parse tree for the mixin/function.
|
59
|
+
Callable = Struct.new(:name, :args, :environment, :tree)
|
57
60
|
|
58
61
|
# This class handles the parsing and compilation of the Sass template.
|
59
62
|
# Example usage:
|
@@ -635,6 +638,8 @@ WARNING
|
|
635
638
|
parse_mixin_definition(line)
|
636
639
|
elsif directive == "include"
|
637
640
|
parse_mixin_include(line, root)
|
641
|
+
elsif directive == "function"
|
642
|
+
parse_function(line, root)
|
638
643
|
elsif directive == "for"
|
639
644
|
parse_for(line, root, value)
|
640
645
|
elsif directive == "each"
|
@@ -665,6 +670,12 @@ WARNING
|
|
665
670
|
:line => @line + 1) unless line.children.empty?
|
666
671
|
offset = line.offset + line.text.index(value).to_i
|
667
672
|
Tree::WarnNode.new(parse_script(value, :offset => offset))
|
673
|
+
elsif directive == "return"
|
674
|
+
raise SyntaxError.new("Invalid @return: expected expression.") unless value
|
675
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
|
676
|
+
:line => @line + 1) unless line.children.empty?
|
677
|
+
offset = line.offset + line.text.index(value).to_i
|
678
|
+
Tree::ReturnNode.new(parse_script(value, :offset => offset))
|
668
679
|
elsif directive == "charset"
|
669
680
|
name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
|
670
681
|
raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
|
@@ -787,7 +798,6 @@ WARNING
|
|
787
798
|
offset = line.offset + line.text.size - arg_string.size
|
788
799
|
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
789
800
|
parse_mixin_definition_arglist
|
790
|
-
default_arg_found = false
|
791
801
|
Tree::MixinDefNode.new(name, args)
|
792
802
|
end
|
793
803
|
|
@@ -804,6 +814,17 @@ WARNING
|
|
804
814
|
Tree::MixinNode.new(name, args, keywords)
|
805
815
|
end
|
806
816
|
|
817
|
+
FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
818
|
+
def parse_function(line, root)
|
819
|
+
name, arg_string = line.text.scan(FUNCTION_RE).first
|
820
|
+
raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
|
821
|
+
|
822
|
+
offset = line.offset + line.text.size - arg_string.size
|
823
|
+
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
824
|
+
parse_function_definition_arglist
|
825
|
+
Tree::FunctionNode.new(name, args)
|
826
|
+
end
|
827
|
+
|
807
828
|
def parse_script(script, options = {})
|
808
829
|
line = options[:line] || @line
|
809
830
|
offset = options[:offset] || 0
|
data/lib/sass/environment.rb
CHANGED
@@ -2,7 +2,7 @@ require 'set'
|
|
2
2
|
|
3
3
|
module Sass
|
4
4
|
# The lexical environment for SassScript.
|
5
|
-
# This keeps track of variable and
|
5
|
+
# This keeps track of variable, mixin, and function definitions.
|
6
6
|
#
|
7
7
|
# A new environment is created for each level of Sass nesting.
|
8
8
|
# This allows variables to be lexically scoped.
|
@@ -146,7 +146,10 @@ RUBY
|
|
146
146
|
# Script::Literal
|
147
147
|
inherited_hash :var
|
148
148
|
# mixin
|
149
|
-
#
|
149
|
+
# Sass::Callable
|
150
150
|
inherited_hash :mixin
|
151
|
+
# function
|
152
|
+
# Sass::Callable
|
153
|
+
inherited_hash :function
|
151
154
|
end
|
152
155
|
end
|
data/lib/sass/plugin/compiler.rb
CHANGED
@@ -213,13 +213,19 @@ module Sass::Plugin
|
|
213
213
|
begin
|
214
214
|
require 'fssm'
|
215
215
|
rescue LoadError => e
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
216
|
+
dir = Sass::Util.scope("vendor/fssm/lib")
|
217
|
+
if $LOAD_PATH.include?(dir)
|
218
|
+
e.message << "\n" <<
|
219
|
+
if File.exists?(scope(".git"))
|
220
|
+
'Run "git submodule update --init" to get the recommended version.'
|
221
|
+
else
|
222
|
+
'Run "gem install fssm" to get it.'
|
223
|
+
end
|
224
|
+
raise e
|
225
|
+
else
|
226
|
+
$LOAD_PATH.unshift dir
|
227
|
+
retry
|
228
|
+
end
|
223
229
|
end
|
224
230
|
|
225
231
|
unless individual_files.empty? && FSSM::Backends::Default.name == "FSSM::Backends::FSEvents"
|
data/lib/sass/script/funcall.rb
CHANGED
@@ -76,6 +76,10 @@ module Sass
|
|
76
76
|
def _perform(environment)
|
77
77
|
args = @args.map {|a| a.perform(environment)}
|
78
78
|
keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
|
79
|
+
if fn = environment.function(@name)
|
80
|
+
return perform_sass_fn(fn, args, keywords)
|
81
|
+
end
|
82
|
+
|
79
83
|
ruby_name = @name.tr('-', '_')
|
80
84
|
args = construct_ruby_args(ruby_name, args, keywords)
|
81
85
|
|
@@ -89,6 +93,8 @@ module Sass
|
|
89
93
|
raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
|
90
94
|
end
|
91
95
|
|
96
|
+
private
|
97
|
+
|
92
98
|
def construct_ruby_args(name, args, keywords)
|
93
99
|
return args if keywords.empty?
|
94
100
|
unless signature = Functions.signature(name.to_sym, args.size, keywords.size)
|
@@ -113,6 +119,30 @@ module Sass
|
|
113
119
|
|
114
120
|
args
|
115
121
|
end
|
122
|
+
|
123
|
+
def perform_sass_fn(function, args, keywords)
|
124
|
+
# TODO: merge with mixin arg evaluation?
|
125
|
+
keywords.each do |name, value|
|
126
|
+
# TODO: Make this fast
|
127
|
+
unless function.args.find {|(var, default)| var.underscored_name == name}
|
128
|
+
raise Sass::SyntaxError.new("Function #{@name} doesn't have an argument named $#{name}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
environment = function.args.zip(args).
|
133
|
+
inject(Sass::Environment.new(function.environment)) do |env, ((var, default), value)|
|
134
|
+
env.set_local_var(var.name,
|
135
|
+
value || keywords[var.underscored_name] || (default && default.perform(env)))
|
136
|
+
raise Sass::SyntaxError.new("Function #{@name} is missing parameter #{var.inspect}.") unless env.var(var.name)
|
137
|
+
env
|
138
|
+
end
|
139
|
+
|
140
|
+
val = catch :_sass_return do
|
141
|
+
function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, environment)}
|
142
|
+
raise Sass::SyntaxError.new("Function #{@name} finished without @return")
|
143
|
+
end
|
144
|
+
val
|
145
|
+
end
|
116
146
|
end
|
117
147
|
end
|
118
148
|
end
|
data/lib/sass/script/parser.rb
CHANGED
@@ -112,6 +112,24 @@ module Sass
|
|
112
112
|
raise e
|
113
113
|
end
|
114
114
|
|
115
|
+
# Parses the argument list for a function definition.
|
116
|
+
#
|
117
|
+
# @return [Array<Script::Node>] The root nodes of the arguments.
|
118
|
+
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
|
119
|
+
def parse_function_definition_arglist
|
120
|
+
args = defn_arglist!(true)
|
121
|
+
assert_done
|
122
|
+
|
123
|
+
args.each do |k, v|
|
124
|
+
k.options = @options
|
125
|
+
v.options = @options if v
|
126
|
+
end
|
127
|
+
args
|
128
|
+
rescue Sass::SyntaxError => e
|
129
|
+
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
|
130
|
+
raise e
|
131
|
+
end
|
132
|
+
|
115
133
|
# Parses a SassScript expression.
|
116
134
|
#
|
117
135
|
# @overload parse(str, line, offset, filename = nil)
|
@@ -283,10 +301,16 @@ RUBY
|
|
283
301
|
node(Script::Funcall.new(tok.value, args, keywords))
|
284
302
|
end
|
285
303
|
|
286
|
-
def defn_arglist!(
|
287
|
-
|
304
|
+
def defn_arglist!(must_have_parens)
|
305
|
+
if must_have_parens
|
306
|
+
assert_tok(:lparen)
|
307
|
+
else
|
308
|
+
return [] unless try_tok(:lparen)
|
309
|
+
end
|
288
310
|
return [] if try_tok(:rparen)
|
311
|
+
|
289
312
|
res = []
|
313
|
+
must_have_default = false
|
290
314
|
loop do
|
291
315
|
line = @lexer.line
|
292
316
|
offset = @lexer.offset + 1
|
data/lib/sass/scss/parser.rb
CHANGED
@@ -98,8 +98,8 @@ module Sass
|
|
98
98
|
node << comment
|
99
99
|
end
|
100
100
|
|
101
|
-
DIRECTIVES = Set[:mixin, :include, :
|
102
|
-
:else, :extend, :import, :media, :charset]
|
101
|
+
DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
|
102
|
+
:each, :while, :if, :else, :extend, :import, :media, :charset]
|
103
103
|
|
104
104
|
def directive
|
105
105
|
return unless tok(/@/)
|
@@ -144,6 +144,17 @@ module Sass
|
|
144
144
|
node(Sass::Tree::MixinNode.new(name, args, keywords))
|
145
145
|
end
|
146
146
|
|
147
|
+
def function_directive
|
148
|
+
name = tok! IDENT
|
149
|
+
args = sass_script(:parse_function_definition_arglist)
|
150
|
+
ss
|
151
|
+
block(node(Sass::Tree::FunctionNode.new(name, args)), :function)
|
152
|
+
end
|
153
|
+
|
154
|
+
def return_directive
|
155
|
+
node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
|
156
|
+
end
|
157
|
+
|
147
158
|
def debug_directive
|
148
159
|
node(Sass::Tree::DebugNode.new(sass_script(:parse)))
|
149
160
|
end
|
@@ -365,6 +376,7 @@ module Sass
|
|
365
376
|
end
|
366
377
|
|
367
378
|
def block_child(context)
|
379
|
+
return variable || directive if context == :function
|
368
380
|
return variable || directive || ruleset if context == :stylesheet
|
369
381
|
variable || directive || declaration_or_ruleset
|
370
382
|
end
|
data/lib/sass/tree/each_node.rb
CHANGED
@@ -20,19 +20,5 @@ module Sass::Tree
|
|
20
20
|
@list = list
|
21
21
|
super()
|
22
22
|
end
|
23
|
-
|
24
|
-
protected
|
25
|
-
|
26
|
-
# Returns an error message if the given child node is invalid,
|
27
|
-
# and false otherwise.
|
28
|
-
#
|
29
|
-
# {ExtendNode}s are valid within {EachNode}s.
|
30
|
-
#
|
31
|
-
# @param child [Tree::Node] A potential child node.
|
32
|
-
# @return [Boolean, String] Whether or not the child node is valid,
|
33
|
-
# as well as the error message to display if it is invalid
|
34
|
-
def invalid_child?(child)
|
35
|
-
super unless child.is_a?(ExtendNode)
|
36
|
-
end
|
37
23
|
end
|
38
24
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Sass
|
2
|
+
module Tree
|
3
|
+
# A dynamic node representing a function definition.
|
4
|
+
#
|
5
|
+
# @see Sass::Tree
|
6
|
+
class FunctionNode < Node
|
7
|
+
# The name of the function.
|
8
|
+
# @return [String]
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# The arguments to the function. Each element is a tuple
|
12
|
+
# containing the variable for argument and the parse tree for
|
13
|
+
# the default value of the argument
|
14
|
+
#
|
15
|
+
# @return [Array<Script::Node>]
|
16
|
+
attr_reader :args
|
17
|
+
|
18
|
+
# @param name [String] The function name
|
19
|
+
# @param args [Array<(Script::Node, Script::Node)>] The arguments for the function.
|
20
|
+
def initialize(name, args)
|
21
|
+
@name = name
|
22
|
+
@args = args
|
23
|
+
super()
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/sass/tree/node.rb
CHANGED
@@ -86,29 +86,16 @@ module Sass
|
|
86
86
|
#
|
87
87
|
# @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
|
88
88
|
# @raise [Sass::SyntaxError] if `child` is invalid
|
89
|
-
# @see #invalid_child?
|
90
89
|
def <<(child)
|
91
90
|
return if child.nil?
|
92
91
|
if child.is_a?(Array)
|
93
92
|
child.each {|c| self << c}
|
94
93
|
else
|
95
|
-
check_child! child
|
96
94
|
self.has_children = true
|
97
95
|
@children << child
|
98
96
|
end
|
99
97
|
end
|
100
98
|
|
101
|
-
# Raises an error if the given child node is invalid.
|
102
|
-
#
|
103
|
-
# @param child [Tree::Node] The child node
|
104
|
-
# @raise [Sass::SyntaxError] if `child` is invalid
|
105
|
-
# @see #invalid_child?
|
106
|
-
def check_child!(child)
|
107
|
-
if msg = invalid_child?(child)
|
108
|
-
raise Sass::SyntaxError.new(msg, :line => child.line)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
99
|
# Compares this node and another object (only other {Tree::Node}s will be equal).
|
113
100
|
# This does a structural comparison;
|
114
101
|
# if the contents of the nodes and all the child nodes are equivalent,
|
@@ -227,26 +214,6 @@ module Sass
|
|
227
214
|
return res if res
|
228
215
|
raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line)
|
229
216
|
end
|
230
|
-
|
231
|
-
# Returns an error message if the given child node is invalid,
|
232
|
-
# and false otherwise.
|
233
|
-
#
|
234
|
-
# By default, all child nodes except those only allowed under specific nodes
|
235
|
-
# ({Tree::MixinDefNode}, {Tree::ImportNode}, {Tree::ExtendNode}) are valid.
|
236
|
-
# This is expected to be overriden by subclasses
|
237
|
-
# for which some children are invalid.
|
238
|
-
#
|
239
|
-
# @param child [Tree::Node] A potential child node
|
240
|
-
# @return [Boolean, String] Whether or not the child node is valid,
|
241
|
-
# as well as the error message to display if it is invalid
|
242
|
-
def invalid_child?(child)
|
243
|
-
case child
|
244
|
-
when Tree::MixinDefNode
|
245
|
-
"Mixins may only be defined at the root of a document."
|
246
|
-
when Tree::ImportNode
|
247
|
-
"Import directives may only be used at the root of a document."
|
248
|
-
end
|
249
|
-
end
|
250
217
|
end
|
251
218
|
end
|
252
219
|
end
|
data/lib/sass/tree/prop_node.rb
CHANGED
@@ -92,18 +92,6 @@ module Sass::Tree
|
|
92
92
|
"#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip
|
93
93
|
end
|
94
94
|
|
95
|
-
# Returns an error message if the given child node is invalid,
|
96
|
-
# and false otherwise.
|
97
|
-
#
|
98
|
-
# {PropNode} only allows other {PropNode}s and {CommentNode}s as children.
|
99
|
-
# @param child [Tree::Node] A potential child node
|
100
|
-
# @return [String] An error message if the child is invalid, or nil otherwise
|
101
|
-
def invalid_child?(child)
|
102
|
-
if !child.is_a?(PropNode) && !child.is_a?(CommentNode)
|
103
|
-
"Illegal nesting: Only properties may be nested beneath properties."
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
95
|
private
|
108
96
|
|
109
97
|
def check!
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Sass
|
2
|
+
module Tree
|
3
|
+
# A dynamic node representing returning from a function.
|
4
|
+
#
|
5
|
+
# @see Sass::Tree
|
6
|
+
class ReturnNode < Node
|
7
|
+
# The expression to return.
|
8
|
+
# @type [Script::Node]
|
9
|
+
attr_reader :expr
|
10
|
+
|
11
|
+
# @param expr [Script::Node] The expression to return
|
12
|
+
def initialize(expr)
|
13
|
+
@expr = expr
|
14
|
+
super()
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/sass/tree/root_node.rb
CHANGED
@@ -16,28 +16,13 @@ module Sass
|
|
16
16
|
# Runs the dynamic Sass code *and* computes the CSS for the tree.
|
17
17
|
# @see #to_s
|
18
18
|
def render
|
19
|
-
|
19
|
+
Visitors::CheckNesting.visit(self)
|
20
|
+
result = Visitors::Perform.visit(self)
|
21
|
+
Visitors::CheckNesting.visit(result) # Check again to validate mixins
|
22
|
+
result, extends = Visitors::Cssize.visit(result)
|
20
23
|
result = result.do_extend(extends) unless extends.empty?
|
21
24
|
result.to_s
|
22
25
|
end
|
23
|
-
|
24
|
-
# Returns an error message if the given child node is invalid,
|
25
|
-
# and false otherwise.
|
26
|
-
#
|
27
|
-
# Only property nodes are invalid at root level.
|
28
|
-
#
|
29
|
-
# @see Node#invalid_child?
|
30
|
-
def invalid_child?(child)
|
31
|
-
case child
|
32
|
-
when Tree::ExtendNode
|
33
|
-
"Extend directives may only be used within rules."
|
34
|
-
when Tree::PropNode
|
35
|
-
"Properties aren't allowed at the root of a document." +
|
36
|
-
child.pseudo_class_selector_message
|
37
|
-
else
|
38
|
-
return
|
39
|
-
end
|
40
|
-
end
|
41
26
|
end
|
42
27
|
end
|
43
28
|
end
|
@@ -32,7 +32,7 @@ module Sass::Tree::Visitors
|
|
32
32
|
# @param node [Tree::Node] The node to visit.
|
33
33
|
# @return [Object] The return value of the `visit_*` method for this node.
|
34
34
|
def visit(node)
|
35
|
-
method = "visit_#{node
|
35
|
+
method = "visit_#{node_name node}"
|
36
36
|
if self.respond_to?(method)
|
37
37
|
self.send(method, node) {visit_children(node)}
|
38
38
|
else
|
@@ -53,6 +53,14 @@ module Sass::Tree::Visitors
|
|
53
53
|
parent.children.map {|c| visit(c)}
|
54
54
|
end
|
55
55
|
|
56
|
+
# Returns the name of a node as used in the `visit_*` method.
|
57
|
+
#
|
58
|
+
# @param [Tree::Node] node The node.
|
59
|
+
# @return [String] The name.
|
60
|
+
def node_name(node)
|
61
|
+
node.class.name.gsub(/.*::(.*?)Node$/, '\\1').downcase
|
62
|
+
end
|
63
|
+
|
56
64
|
# `yield`s, then runs the visitor on the `@else` clause if the node has one.
|
57
65
|
# This exists to ensure that the contents of the `@else` clause get visited.
|
58
66
|
def visit_if(node)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# A visitor for checking that all nodes are properly nested.
|
2
|
+
class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
|
3
|
+
protected
|
4
|
+
|
5
|
+
def visit(node)
|
6
|
+
if error = @parent && (
|
7
|
+
try_send("invalid_#{node_name @parent}_child?", @parent, node) ||
|
8
|
+
try_send("invalid_#{node_name node}_parent?", @parent, node))
|
9
|
+
raise Sass::SyntaxError.new(error)
|
10
|
+
end
|
11
|
+
super
|
12
|
+
rescue Sass::SyntaxError => e
|
13
|
+
e.modify_backtrace(:filename => node.filename, :line => node.line)
|
14
|
+
raise e
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_children(parent)
|
18
|
+
old_parent = @parent
|
19
|
+
@parent = parent unless is_any_of?(parent,
|
20
|
+
Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
|
21
|
+
Sass::Tree::ImportNode, Sass::Tree::MixinNode, Sass::Tree::WhileNode)
|
22
|
+
super
|
23
|
+
ensure
|
24
|
+
@parent = old_parent
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_root(node)
|
28
|
+
yield
|
29
|
+
rescue Sass::SyntaxError => e
|
30
|
+
e.sass_template ||= node.template
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_import(node)
|
35
|
+
yield
|
36
|
+
rescue Sass::SyntaxError => e
|
37
|
+
e.modify_backtrace(:filename => node.children.first.filename)
|
38
|
+
e.add_backtrace(:filename => node.filename, :line => node.line)
|
39
|
+
raise e
|
40
|
+
end
|
41
|
+
|
42
|
+
def invalid_charset_parent?(parent, child)
|
43
|
+
"@charset may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
|
44
|
+
end
|
45
|
+
|
46
|
+
def invalid_extend_parent?(parent, child)
|
47
|
+
unless is_any_of?(parent, Sass::Tree::RuleNode, Sass::Tree::MixinDefNode)
|
48
|
+
"Extend directives may only be used within rules."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def invalid_function_parent?(parent, child)
|
53
|
+
"Functions may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
|
54
|
+
end
|
55
|
+
|
56
|
+
def invalid_function_child?(parent, child)
|
57
|
+
unless is_any_of?(child,
|
58
|
+
Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::EachNode,
|
59
|
+
Sass::Tree::ForNode, Sass::Tree::IfNode, Sass::Tree::ReturnNode,
|
60
|
+
Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::WhileNode)
|
61
|
+
"Functions can only contain variable declarations and control directives."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def invalid_import_parent?(parent, child)
|
66
|
+
"Import directives may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
|
67
|
+
end
|
68
|
+
|
69
|
+
def invalid_mixindef_parent?(parent, child)
|
70
|
+
"Mixins may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
|
71
|
+
end
|
72
|
+
|
73
|
+
def invalid_prop_child?(parent, child)
|
74
|
+
unless is_any_of?(child, Sass::Tree::CommentNode, Sass::Tree::PropNode)
|
75
|
+
"Illegal nesting: Only properties may be nested beneath properties."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def invalid_prop_parent?(parent, child)
|
80
|
+
unless is_any_of?(parent,
|
81
|
+
Sass::Tree::RuleNode, Sass::Tree::PropNode,
|
82
|
+
Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode)
|
83
|
+
"Properties are only allowed within rules, directives, or other properties." + child.pseudo_class_selector_message
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def invalid_return_parent?(parent, child)
|
88
|
+
"@return may only be used within a function." unless parent.is_a?(Sass::Tree::FunctionNode)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def is_any_of?(val, *classes)
|
94
|
+
classes.any? {|c| val.is_a?(c)}
|
95
|
+
end
|
96
|
+
|
97
|
+
def try_send(method, *args, &block)
|
98
|
+
return unless respond_to?(method)
|
99
|
+
send(method, *args, &block)
|
100
|
+
end
|
101
|
+
end
|
@@ -110,6 +110,14 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
110
110
|
"#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
|
111
111
|
end
|
112
112
|
|
113
|
+
def visit_function(node)
|
114
|
+
args = node.args.map do |v, d|
|
115
|
+
d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
|
116
|
+
end.join(", ")
|
117
|
+
|
118
|
+
"#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"
|
119
|
+
end
|
120
|
+
|
113
121
|
def visit_if(node)
|
114
122
|
name =
|
115
123
|
if !@is_else; "if"
|
@@ -167,6 +175,10 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
167
175
|
res + yield.rstrip + semi + "\n"
|
168
176
|
end
|
169
177
|
|
178
|
+
def visit_return(node)
|
179
|
+
"#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n"
|
180
|
+
end
|
181
|
+
|
170
182
|
def visit_rule(node)
|
171
183
|
if @format == :sass
|
172
184
|
name = selector_to_sass(node.rule)
|
@@ -123,11 +123,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
|
|
123
123
|
|
124
124
|
# Asserts that all the mixin's children are valid in their new location.
|
125
125
|
def visit_mixin(node)
|
126
|
-
node
|
127
|
-
|
128
|
-
# Don't use #visit_children to avoid adding the mixin node to the list of parents.
|
129
|
-
visit(c)
|
130
|
-
end.flatten
|
126
|
+
# Don't use #visit_children to avoid adding the mixin node to the list of parents.
|
127
|
+
node.children.map {|c| visit(c)}.flatten
|
131
128
|
rescue Sass::SyntaxError => e
|
132
129
|
e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
|
133
130
|
e.add_backtrace(:filename => node.filename, :line => node.line)
|
@@ -1,9 +1,16 @@
|
|
1
1
|
# A visitor for converting a dynamic Sass tree into a static Sass tree.
|
2
2
|
class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
3
|
+
# @param root [Tree::Node] The root node of the tree to visit.
|
4
|
+
# @param environment [Sass::Environment] The lexical environment.
|
5
|
+
# @return [Tree::Node] The resulting tree of static nodes.
|
6
|
+
def self.visit(root, environment = Sass::Environment.new)
|
7
|
+
new(environment).send(:visit, root)
|
8
|
+
end
|
9
|
+
|
3
10
|
protected
|
4
11
|
|
5
|
-
def initialize
|
6
|
-
@environment =
|
12
|
+
def initialize(env)
|
13
|
+
@environment = env
|
7
14
|
end
|
8
15
|
|
9
16
|
# If an exception is raised, this add proper metadata to the backtrace.
|
@@ -18,7 +25,6 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
18
25
|
def visit_children(parent)
|
19
26
|
with_environment Sass::Environment.new(@environment) do
|
20
27
|
parent.children = super.flatten
|
21
|
-
parent.children.each {|c| parent.check_child! c}
|
22
28
|
parent
|
23
29
|
end
|
24
30
|
end
|
@@ -100,6 +106,13 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
100
106
|
end
|
101
107
|
end
|
102
108
|
|
109
|
+
# Loads the function into the environment.
|
110
|
+
def visit_function(node)
|
111
|
+
@environment.set_function(node.name,
|
112
|
+
Sass::Callable.new(node.name, node.args, @environment, node.children))
|
113
|
+
[]
|
114
|
+
end
|
115
|
+
|
103
116
|
# Runs the child nodes if the conditional expression is true;
|
104
117
|
# otherwise, tries the else nodes.
|
105
118
|
def visit_if(node)
|
@@ -135,7 +148,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
135
148
|
# Loads a mixin into the environment.
|
136
149
|
def visit_mixindef(node)
|
137
150
|
@environment.set_mixin(node.name,
|
138
|
-
Sass::
|
151
|
+
Sass::Callable.new(node.name, node.args, @environment, node.children))
|
139
152
|
[]
|
140
153
|
end
|
141
154
|
|
@@ -206,6 +219,11 @@ END
|
|
206
219
|
yield
|
207
220
|
end
|
208
221
|
|
222
|
+
# Returns the value of the expression.
|
223
|
+
def visit_return(node)
|
224
|
+
throw :_sass_return, node.expr.perform(@environment)
|
225
|
+
end
|
226
|
+
|
209
227
|
# Runs SassScript interpolation in the selector,
|
210
228
|
# and then parses the result into a {Sass::Selector::CommaSequence}.
|
211
229
|
def visit_rule(node)
|
@@ -901,6 +901,42 @@ foo {
|
|
901
901
|
SCSS
|
902
902
|
end
|
903
903
|
|
904
|
+
def test_argless_function_definition
|
905
|
+
assert_renders <<SASS, <<SCSS
|
906
|
+
@function foo()
|
907
|
+
$var: 1 + 1
|
908
|
+
@return $var
|
909
|
+
SASS
|
910
|
+
@function foo() {
|
911
|
+
$var: 1 + 1;
|
912
|
+
@return $var; }
|
913
|
+
SCSS
|
914
|
+
end
|
915
|
+
|
916
|
+
def test_function_definition_without_defaults
|
917
|
+
assert_renders <<SASS, <<SCSS
|
918
|
+
@function foo($var1, $var2)
|
919
|
+
@if $var1
|
920
|
+
@return $var1 + $var2
|
921
|
+
SASS
|
922
|
+
@function foo($var1, $var2) {
|
923
|
+
@if $var1 {
|
924
|
+
@return $var1 + $var2; } }
|
925
|
+
SCSS
|
926
|
+
end
|
927
|
+
|
928
|
+
def test_function_definition_with_defaults
|
929
|
+
assert_renders <<SASS, <<SCSS
|
930
|
+
@function foo($var1, $var2: foo)
|
931
|
+
@if $var1
|
932
|
+
@return $var1 + $var2
|
933
|
+
SASS
|
934
|
+
@function foo($var1, $var2: foo) {
|
935
|
+
@if $var1 {
|
936
|
+
@return $var1 + $var2; } }
|
937
|
+
SCSS
|
938
|
+
end
|
939
|
+
|
904
940
|
def test_variable_definition
|
905
941
|
assert_renders <<SASS, <<SCSS
|
906
942
|
$var1: 12px + 15px
|
data/test/sass/engine_test.rb
CHANGED
@@ -39,8 +39,8 @@ MSG
|
|
39
39
|
"a\n b: c;" => 'Invalid CSS after "c": expected expression (e.g. 1px, bold), was ";"',
|
40
40
|
".foo ^bar\n a: b" => ['Invalid CSS after ".foo ": expected selector, was "^bar"', 1],
|
41
41
|
"a\n @extend .foo ^bar" => 'Invalid CSS after ".foo ": expected selector, was "^bar"',
|
42
|
-
"a: b" => 'Properties
|
43
|
-
":a b" => 'Properties
|
42
|
+
"a: b" => 'Properties are only allowed within rules, directives, or other properties.',
|
43
|
+
":a b" => 'Properties are only allowed within rules, directives, or other properties.',
|
44
44
|
"!" => 'Invalid variable: "!".',
|
45
45
|
"$a" => 'Invalid variable: "$a".',
|
46
46
|
"! a" => 'Invalid variable: "! a".',
|
@@ -87,12 +87,26 @@ MSG
|
|
87
87
|
"=a(,)" => 'Invalid CSS after "(": expected variable (e.g. $foo), was ",)"',
|
88
88
|
"=a($)" => 'Invalid CSS after "(": expected variable (e.g. $foo), was "$)"',
|
89
89
|
"=a($foo bar)" => 'Invalid CSS after "($foo ": expected ")", was "bar)"',
|
90
|
-
"=foo\n bar: baz\n+foo" => ["Properties
|
90
|
+
"=foo\n bar: baz\n+foo" => ["Properties are only allowed within rules, directives, or other properties.", 2],
|
91
91
|
"a-\#{$b\n c: d" => ['Invalid CSS after "a-#{$b": expected "}", was ""', 1],
|
92
92
|
"=a($b = 1, $c)" => "Required argument $c must come before any optional arguments.",
|
93
93
|
"=a($b = 1)\n a: $b\ndiv\n +a(1,2)" => "Mixin a takes 1 argument but 2 were passed.",
|
94
94
|
"=a($b: 1)\n a: $b\ndiv\n +a(1,$c: 3)" => "Mixin a doesn't have an argument named $c",
|
95
95
|
"=a($b)\n a: $b\ndiv\n +a" => "Mixin a is missing parameter $b.",
|
96
|
+
"@function foo()\n 1 + 2" => "Functions can only contain variable declarations and control directives.",
|
97
|
+
"@function foo()\n foo: bar" => "Functions can only contain variable declarations and control directives.",
|
98
|
+
"@function foo()\n foo: bar\n @return 3" => ["Functions can only contain variable declarations and control directives.", 2],
|
99
|
+
"@function foo\n @return 1" => ['Invalid CSS after "": expected "(", was ""', 1],
|
100
|
+
"@function foo(\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was ""', 1],
|
101
|
+
"@function foo(b)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "b)"', 1],
|
102
|
+
"@function foo(,)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was ",)"', 1],
|
103
|
+
"@function foo($)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "$)"', 1],
|
104
|
+
"@function foo()\n @return" => 'Invalid @return: expected expression.',
|
105
|
+
"@function foo()\n @return 1\n $var: val" => 'Illegal nesting: Nothing may be nested beneath return directives.',
|
106
|
+
"foo\n @function bar()\n @return 1" => ['Functions may only be defined at the root of a document.', 2],
|
107
|
+
"@return 1" => '@return may only be used within a function.',
|
108
|
+
"@if true\n @return 1" => '@return may only be used within a function.',
|
109
|
+
"@mixin foo\n @return 1\n@include foo" => ['@return may only be used within a function.', 2],
|
96
110
|
"@else\n a\n b: c" => ["@else must come after @if.", 1],
|
97
111
|
"@if false\n@else foo" => "Invalid else directive '@else foo': expected 'if <expr>'.",
|
98
112
|
"@if false\n@else if " => "Invalid else directive '@else if': expected 'if <expr>'.",
|
@@ -511,7 +525,7 @@ SASS
|
|
511
525
|
rescue Sass::SyntaxError => e
|
512
526
|
assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e, opts).split("\n")[0..11].join("\n"))
|
513
527
|
/*
|
514
|
-
Syntax error: Properties
|
528
|
+
Syntax error: Properties are only allowed within rules, directives, or other properties.
|
515
529
|
on line 4 of test_cssize_exception_css_inline.sass
|
516
530
|
|
517
531
|
1: .filler
|
@@ -1127,6 +1141,90 @@ a
|
|
1127
1141
|
SASS
|
1128
1142
|
end
|
1129
1143
|
|
1144
|
+
def test_basic_function
|
1145
|
+
assert_equal(<<CSS, render(<<SASS))
|
1146
|
+
bar {
|
1147
|
+
a: 3; }
|
1148
|
+
CSS
|
1149
|
+
@function foo()
|
1150
|
+
@return 1 + 2
|
1151
|
+
|
1152
|
+
bar
|
1153
|
+
a: foo()
|
1154
|
+
SASS
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
def test_function_args
|
1158
|
+
assert_equal(<<CSS, render(<<SASS))
|
1159
|
+
bar {
|
1160
|
+
a: 3; }
|
1161
|
+
CSS
|
1162
|
+
@function plus($var1, $var2)
|
1163
|
+
@return $var1 + $var2
|
1164
|
+
|
1165
|
+
bar
|
1166
|
+
a: plus(1, 2)
|
1167
|
+
SASS
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def test_function_arg_default
|
1171
|
+
assert_equal(<<CSS, render(<<SASS))
|
1172
|
+
bar {
|
1173
|
+
a: 3; }
|
1174
|
+
CSS
|
1175
|
+
@function plus($var1, $var2: 2)
|
1176
|
+
@return $var1 + $var2
|
1177
|
+
|
1178
|
+
bar
|
1179
|
+
a: plus(1)
|
1180
|
+
SASS
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
def test_function_arg_keyword
|
1184
|
+
assert_equal(<<CSS, render(<<SASS))
|
1185
|
+
bar {
|
1186
|
+
a: 1bar; }
|
1187
|
+
CSS
|
1188
|
+
@function plus($var1: 1, $var2: 2)
|
1189
|
+
@return $var1 + $var2
|
1190
|
+
|
1191
|
+
bar
|
1192
|
+
a: plus($var2: bar)
|
1193
|
+
SASS
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
def test_function_with_if
|
1197
|
+
assert_equal(<<CSS, render(<<SASS))
|
1198
|
+
bar {
|
1199
|
+
a: foo;
|
1200
|
+
b: bar; }
|
1201
|
+
CSS
|
1202
|
+
@function my-if($cond, $val1, $val2)
|
1203
|
+
@if $cond
|
1204
|
+
@return $val1
|
1205
|
+
@else
|
1206
|
+
@return $val2
|
1207
|
+
|
1208
|
+
bar
|
1209
|
+
a: my-if(true, foo, bar)
|
1210
|
+
b: my-if(false, foo, bar)
|
1211
|
+
SASS
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
def test_function_with_var
|
1215
|
+
assert_equal(<<CSS, render(<<SASS))
|
1216
|
+
bar {
|
1217
|
+
a: 1; }
|
1218
|
+
CSS
|
1219
|
+
@function foo($val1, $val2)
|
1220
|
+
$intermediate: $val1 + $val2
|
1221
|
+
@return $intermediate/3
|
1222
|
+
|
1223
|
+
bar
|
1224
|
+
a: foo(1, 2)
|
1225
|
+
SASS
|
1226
|
+
end
|
1227
|
+
|
1130
1228
|
def test_interpolation
|
1131
1229
|
assert_equal("a-1 {\n b-2-3: c-3; }\n", render(<<SASS))
|
1132
1230
|
$a: 1
|
data/test/sass/scss/scss_test.rb
CHANGED
@@ -699,6 +699,38 @@ CSS
|
|
699
699
|
SCSS
|
700
700
|
end
|
701
701
|
|
702
|
+
## Functions
|
703
|
+
|
704
|
+
def test_basic_function
|
705
|
+
assert_equal(<<CSS, render(<<SASS))
|
706
|
+
bar {
|
707
|
+
a: 3; }
|
708
|
+
CSS
|
709
|
+
@function foo() {
|
710
|
+
@return 1 + 2;
|
711
|
+
}
|
712
|
+
|
713
|
+
bar {
|
714
|
+
a: foo();
|
715
|
+
}
|
716
|
+
SASS
|
717
|
+
end
|
718
|
+
|
719
|
+
def test_function_args
|
720
|
+
assert_equal(<<CSS, render(<<SASS))
|
721
|
+
bar {
|
722
|
+
a: 3; }
|
723
|
+
CSS
|
724
|
+
@function plus($var1, $var2) {
|
725
|
+
@return $var1 + $var2;
|
726
|
+
}
|
727
|
+
|
728
|
+
bar {
|
729
|
+
a: plus(1, 2);
|
730
|
+
}
|
731
|
+
SASS
|
732
|
+
end
|
733
|
+
|
702
734
|
## Interpolation
|
703
735
|
|
704
736
|
def test_basic_selector_interpolation
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.0.alpha.
|
4
|
+
version: 3.1.0.alpha.51
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Weizenbaum
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2010-12-
|
14
|
+
date: 2010-12-07 00:00:00 -05:00
|
15
15
|
default_executable:
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
@@ -133,6 +133,9 @@ files:
|
|
133
133
|
- lib/sass/tree/visitors/cssize.rb
|
134
134
|
- lib/sass/tree/visitors/perform.rb
|
135
135
|
- lib/sass/tree/visitors/to_css.rb
|
136
|
+
- lib/sass/tree/visitors/check_nesting.rb
|
137
|
+
- lib/sass/tree/function_node.rb
|
138
|
+
- lib/sass/tree/return_node.rb
|
136
139
|
- lib/sass/util.rb
|
137
140
|
- lib/sass/util/subset_map.rb
|
138
141
|
- lib/sass/version.rb
|