sass 3.1.0.alpha.50 → 3.1.0.alpha.51
Sign up to get free protection for your applications and to get access to all the features.
- 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
|