sass 3.2.0.alpha.21 → 3.2.0.alpha.33
Sign up to get free protection for your applications and to get access to all the features.
- data/REVISION +1 -1
- data/VERSION +1 -1
- data/lib/sass/engine.rb +29 -11
- data/lib/sass/environment.rb +1 -71
- data/lib/sass/exec.rb +1 -1
- data/lib/sass/less.rb +1 -1
- data/lib/sass/scss/parser.rb +24 -8
- data/lib/sass/shared.rb +1 -1
- data/lib/sass/tree/comment_node.rb +31 -26
- data/lib/sass/tree/visitors/convert.rb +15 -16
- data/lib/sass/tree/visitors/deep_copy.rb +1 -1
- data/lib/sass/tree/visitors/perform.rb +57 -28
- data/lib/sass/tree/visitors/set_options.rb +1 -1
- data/lib/sass/tree/visitors/to_css.rb +3 -13
- data/lib/sass/util.rb +51 -0
- data/test/Gemfile +4 -0
- data/test/Gemfile.lock +19 -0
- data/test/sass/conversion_test.rb +1 -14
- data/test/sass/engine_test.rb +28 -30
- data/test/sass/plugin_test.rb +1 -1
- data/test/sass/results/import_content.css +1 -0
- data/test/sass/scss/css_test.rb +6 -0
- data/test/sass/templates/_imported_content.sass +3 -0
- data/test/sass/templates/bork5.sass +3 -0
- data/test/sass/templates/import_content.sass +4 -0
- data/test/sass/templates/nested_bork5.sass +2 -0
- data/test/sass/util_test.rb +12 -0
- metadata +9 -2
data/REVISION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
d56ebfe074b7f23e689330e32d9dd0dd4f7ec76d
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.2.0.alpha.
|
1
|
+
3.2.0.alpha.33
|
data/lib/sass/engine.rb
CHANGED
@@ -95,7 +95,10 @@ module Sass
|
|
95
95
|
#
|
96
96
|
# `children`: `Array<Line>`
|
97
97
|
# : The lines nested below this one.
|
98
|
-
|
98
|
+
#
|
99
|
+
# `comment_tab_str`: `String?`
|
100
|
+
# : The prefix indentation for this comment, if it is a comment.
|
101
|
+
class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str)
|
99
102
|
def comment?
|
100
103
|
text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
|
101
104
|
end
|
@@ -112,6 +115,10 @@ module Sass
|
|
112
115
|
# which is not output as a CSS comment.
|
113
116
|
SASS_COMMENT_CHAR = ?/
|
114
117
|
|
118
|
+
# The character that indicates that a comment allows interpolation
|
119
|
+
# and should be preserved even in `:compressed` mode.
|
120
|
+
SASS_LOUD_COMMENT_CHAR = ?!
|
121
|
+
|
115
122
|
# The character that follows the general COMMENT_CHAR and designates a CSS comment,
|
116
123
|
# which is embedded in the CSS document.
|
117
124
|
CSS_COMMENT_CHAR = ?*
|
@@ -425,7 +432,8 @@ but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
|
|
425
432
|
MSG
|
426
433
|
end
|
427
434
|
|
428
|
-
last.
|
435
|
+
last.comment_tab_str ||= comment_tab_str
|
436
|
+
last.text << "\n" << line
|
429
437
|
true
|
430
438
|
end
|
431
439
|
|
@@ -488,11 +496,11 @@ MSG
|
|
488
496
|
continued_rule = nil
|
489
497
|
end
|
490
498
|
|
491
|
-
if child.is_a?(Tree::CommentNode) && child.silent
|
499
|
+
if child.is_a?(Tree::CommentNode) && child.type == :silent
|
492
500
|
if continued_comment &&
|
493
501
|
child.line == continued_comment.line +
|
494
|
-
continued_comment.
|
495
|
-
continued_comment.value
|
502
|
+
continued_comment.lines + 1
|
503
|
+
continued_comment.value += ["\n"] + child.value
|
496
504
|
next
|
497
505
|
end
|
498
506
|
|
@@ -543,7 +551,7 @@ WARNING
|
|
543
551
|
when ?$
|
544
552
|
parse_variable(line)
|
545
553
|
when COMMENT_CHAR
|
546
|
-
parse_comment(line
|
554
|
+
parse_comment(line)
|
547
555
|
when DIRECTIVE_CHAR
|
548
556
|
parse_directive(parent, line, root)
|
549
557
|
when ESCAPE_CHAR
|
@@ -605,11 +613,21 @@ WARNING
|
|
605
613
|
end
|
606
614
|
|
607
615
|
def parse_comment(line)
|
608
|
-
if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
|
609
|
-
silent = line[1] == SASS_COMMENT_CHAR
|
610
|
-
|
611
|
-
|
612
|
-
|
616
|
+
if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR
|
617
|
+
silent = line.text[1] == SASS_COMMENT_CHAR
|
618
|
+
loud = !silent && line.text[2] == SASS_LOUD_COMMENT_CHAR
|
619
|
+
if silent
|
620
|
+
value = [line.text]
|
621
|
+
else
|
622
|
+
value = self.class.parse_interp(line.text, line.index, line.offset, :filename => @filename)
|
623
|
+
value[0].slice!(2) if loud # get rid of the "!"
|
624
|
+
end
|
625
|
+
value = with_extracted_values(value) do |str|
|
626
|
+
str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
|
627
|
+
format_comment_text(str, silent)
|
628
|
+
end
|
629
|
+
type = if silent then :silent elsif loud then :loud else :normal end
|
630
|
+
Tree::CommentNode.new(value, type)
|
613
631
|
else
|
614
632
|
Tree::RuleNode.new(parse_interp(line))
|
615
633
|
end
|
data/lib/sass/environment.rb
CHANGED
@@ -25,11 +25,7 @@ module Sass
|
|
25
25
|
# @param parent [Environment] See \{#parent}
|
26
26
|
def initialize(parent = nil)
|
27
27
|
@parent = parent
|
28
|
-
unless parent
|
29
|
-
@stack = []
|
30
|
-
@mixins_in_use = Set.new
|
31
|
-
set_var("important", Script::String.new("!important"))
|
32
|
-
end
|
28
|
+
set_var("important", Script::String.new("!important")) unless parent
|
33
29
|
end
|
34
30
|
|
35
31
|
# The environment of the caller of this environment's mixin or function.
|
@@ -53,72 +49,6 @@ module Sass
|
|
53
49
|
@options || parent_options || {}
|
54
50
|
end
|
55
51
|
|
56
|
-
# Push a new stack frame onto the mixin/include stack.
|
57
|
-
#
|
58
|
-
# @param frame_info [{Symbol => Object}]
|
59
|
-
# Frame information has the following keys:
|
60
|
-
#
|
61
|
-
# `:filename`
|
62
|
-
# : The name of the file in which the lexical scope changed.
|
63
|
-
#
|
64
|
-
# `:mixin`
|
65
|
-
# : The name of the mixin in which the lexical scope changed,
|
66
|
-
# or `nil` if it wasn't within in a mixin.
|
67
|
-
#
|
68
|
-
# `:line`
|
69
|
-
# : The line of the file on which the lexical scope changed. Never nil.
|
70
|
-
def push_frame(frame_info)
|
71
|
-
top_of_stack = stack.last
|
72
|
-
if top_of_stack && top_of_stack.delete(:prepared)
|
73
|
-
top_of_stack.merge!(frame_info)
|
74
|
-
else
|
75
|
-
stack.push(top_of_stack = frame_info)
|
76
|
-
end
|
77
|
-
mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin] && !top_of_stack[:prepared]
|
78
|
-
end
|
79
|
-
|
80
|
-
# Like \{#push\_frame}, but next time a stack frame is pushed,
|
81
|
-
# it will be merged with this frame.
|
82
|
-
#
|
83
|
-
# @param frame_info [{Symbol => Object}] Same as for \{#push\_frame}.
|
84
|
-
def prepare_frame(frame_info)
|
85
|
-
push_frame(frame_info.merge(:prepared => true))
|
86
|
-
end
|
87
|
-
|
88
|
-
# Pop a stack frame from the mixin/include stack.
|
89
|
-
def pop_frame
|
90
|
-
stack.pop if stack.last && stack.last[:prepared]
|
91
|
-
popped = stack.pop
|
92
|
-
mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
|
93
|
-
end
|
94
|
-
|
95
|
-
# A list of stack frames in the mixin/include stack.
|
96
|
-
# The last element in the list is the most deeply-nested frame.
|
97
|
-
#
|
98
|
-
# @return [Array<{Symbol => Object}>] The stack frames,
|
99
|
-
# of the form passed to \{#push\_frame}.
|
100
|
-
def stack
|
101
|
-
@stack ||= @parent.stack
|
102
|
-
end
|
103
|
-
|
104
|
-
# A set of names of mixins currently present in the stack.
|
105
|
-
#
|
106
|
-
# @return [Set<String>] The mixin names.
|
107
|
-
def mixins_in_use
|
108
|
-
@mixins_in_use ||= @parent.mixins_in_use
|
109
|
-
end
|
110
|
-
|
111
|
-
def stack_trace
|
112
|
-
trace = []
|
113
|
-
stack.reverse.each_with_index do |entry, i|
|
114
|
-
msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
|
115
|
-
msg << " of #{entry[:filename] || "an unknown file"}"
|
116
|
-
msg << ", in `#{entry[:mixin]}'" if entry[:mixin]
|
117
|
-
trace << msg
|
118
|
-
end
|
119
|
-
trace
|
120
|
-
end
|
121
|
-
|
122
52
|
private
|
123
53
|
|
124
54
|
def parent_options
|
data/lib/sass/exec.rb
CHANGED
@@ -385,7 +385,7 @@ MSG
|
|
385
385
|
|
386
386
|
dirs, files = @args.map {|name| split_colon_path(name)}.
|
387
387
|
partition {|i, _| File.directory? i}
|
388
|
-
files.map! {|from, to| [from, to || from.gsub(
|
388
|
+
files.map! {|from, to| [from, to || from.gsub(/\.[^.]*?$/, '.css')]}
|
389
389
|
dirs.map! {|from, to| [from, to || from]}
|
390
390
|
::Sass::Plugin.options[:template_location] = dirs
|
391
391
|
|
data/lib/sass/less.rb
CHANGED
@@ -31,7 +31,7 @@ module Less
|
|
31
31
|
WARNING: Sass doesn't support mixing in selector sequences.
|
32
32
|
Replacing "#{sel}" with "@extend #{base}"
|
33
33
|
WARNING
|
34
|
-
env << Node::SassNode.new(Sass::Tree::CommentNode.new("// #{sel};",
|
34
|
+
env << Node::SassNode.new(Sass::Tree::CommentNode.new(["// #{sel};"], :silent))
|
35
35
|
env << Node::SassNode.new(Sass::Tree::ExtendNode.new([base]))
|
36
36
|
end
|
37
37
|
end
|
data/lib/sass/scss/parser.rb
CHANGED
@@ -89,14 +89,24 @@ module Sass
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def process_comment(text, node)
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
92
|
+
silent = text =~ /^\/\//
|
93
|
+
loud = !silent && text =~ %r{^/[/*]!}
|
94
|
+
line = @line - text.count("\n")
|
95
|
+
|
96
|
+
if silent
|
97
|
+
value = [text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */']
|
98
|
+
else
|
99
|
+
value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
|
100
|
+
value[0].slice!(2) if loud # get rid of the "!"
|
101
|
+
value.unshift(@scanner.
|
102
|
+
string[0...@scanner.pos].
|
103
|
+
reverse[/.*?\*\/(.*?)($|\Z)/, 1].
|
104
|
+
reverse.gsub(/[^\s]/, ' '))
|
105
|
+
end
|
106
|
+
|
107
|
+
type = if silent then :silent elsif loud then :loud else :normal end
|
108
|
+
comment = Sass::Tree::CommentNode.new(value, type)
|
109
|
+
comment.line = line
|
100
110
|
node << comment
|
101
111
|
end
|
102
112
|
|
@@ -779,8 +789,14 @@ MESSAGE
|
|
779
789
|
end
|
780
790
|
|
781
791
|
def str?
|
792
|
+
pos = @scanner.pos
|
793
|
+
line = @line
|
782
794
|
@strs.push ""
|
783
795
|
yield && @strs.last
|
796
|
+
rescue Sass::SyntaxError => e
|
797
|
+
@scanner.pos = pos
|
798
|
+
@line = line
|
799
|
+
nil
|
784
800
|
ensure
|
785
801
|
@strs.pop
|
786
802
|
end
|
data/lib/sass/shared.rb
CHANGED
@@ -17,7 +17,7 @@ module Sass
|
|
17
17
|
# @return [String] The text remaining in the scanner after all `#{`s have been processed
|
18
18
|
def handle_interpolation(str)
|
19
19
|
scan = StringScanner.new(str)
|
20
|
-
yield scan while scan.scan(/(.*?)(\\*)\#\{/)
|
20
|
+
yield scan while scan.scan(/(.*?)(\\*)\#\{/m)
|
21
21
|
scan.rest
|
22
22
|
end
|
23
23
|
|
@@ -6,31 +6,31 @@ module Sass::Tree
|
|
6
6
|
# @see Sass::Tree
|
7
7
|
class CommentNode < Node
|
8
8
|
# The text of the comment, not including `/*` and `*/`.
|
9
|
+
# Interspersed with {Sass::Script::Node}s representing `#{}`-interpolation
|
10
|
+
# if this is a loud comment.
|
9
11
|
#
|
10
|
-
# @return [String]
|
12
|
+
# @return [Array<String, Sass::Script::Node>]
|
11
13
|
attr_accessor :value
|
12
14
|
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# irrespective of compilation settings or the comment syntax used.
|
15
|
+
# The text of the comment
|
16
|
+
# after any interpolated SassScript has been resolved.
|
17
|
+
# Only set once \{Tree::Visitors::Perform} has been run.
|
17
18
|
#
|
18
|
-
# @return [
|
19
|
-
attr_accessor :
|
19
|
+
# @return [String]
|
20
|
+
attr_accessor :resolved_value
|
20
21
|
|
21
|
-
#
|
22
|
+
# The type of the comment. `:silent` means it's never output to CSS,
|
23
|
+
# `:normal` means it's output in every compile mode except `:compressed`,
|
24
|
+
# and `:loud` means it's output even in `:compressed`.
|
22
25
|
#
|
23
|
-
# @return [
|
24
|
-
attr_accessor :
|
26
|
+
# @return [Symbol]
|
27
|
+
attr_accessor :type
|
25
28
|
|
26
|
-
# @param value [String] See \{#value}
|
27
|
-
# @param
|
28
|
-
def initialize(value,
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@value = normalize_indentation value
|
32
|
-
@loud = @value =~ %r{^(/[\/\*])?!}
|
33
|
-
@value.sub!("#{$1}!", $1.to_s) if @loud
|
29
|
+
# @param value [Array<String, Sass::Script::Node>] See \{#value}
|
30
|
+
# @param type [Symbol] See \{#type}
|
31
|
+
def initialize(value, type)
|
32
|
+
@value = Sass::Util.with_extracted_values(value) {|str| normalize_indentation str}
|
33
|
+
@type = type
|
34
34
|
super()
|
35
35
|
end
|
36
36
|
|
@@ -40,7 +40,7 @@ module Sass::Tree
|
|
40
40
|
# @return [Boolean] Whether or not this node and the other object
|
41
41
|
# are the same
|
42
42
|
def ==(other)
|
43
|
-
self.class == other.class && value == other.value &&
|
43
|
+
self.class == other.class && value == other.value && type == other.type
|
44
44
|
end
|
45
45
|
|
46
46
|
# Returns `true` if this is a silent comment
|
@@ -50,16 +50,21 @@ module Sass::Tree
|
|
50
50
|
#
|
51
51
|
# @return [Boolean]
|
52
52
|
def invisible?
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
case @type
|
54
|
+
when :loud; false
|
55
|
+
when :silent; true
|
56
|
+
else; style == :compressed
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
# Returns
|
61
|
-
|
62
|
-
|
60
|
+
# Returns the number of lines in the comment.
|
61
|
+
#
|
62
|
+
# @return [Fixnum]
|
63
|
+
def lines
|
64
|
+
@value.inject(0) do |s, e|
|
65
|
+
next s + e.count("\n") if e.is_a?(String)
|
66
|
+
next s
|
67
|
+
end
|
63
68
|
end
|
64
69
|
|
65
70
|
private
|
@@ -32,7 +32,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
32
32
|
visit(child) +
|
33
33
|
if nxt &&
|
34
34
|
(child.is_a?(Sass::Tree::CommentNode) &&
|
35
|
-
child.line + child.
|
35
|
+
child.line + child.lines + 1 == nxt.line) ||
|
36
36
|
(child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
|
37
37
|
child.line + 1 == nxt.line) ||
|
38
38
|
(child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
|
@@ -49,8 +49,13 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def visit_comment(node)
|
52
|
+
value = node.value.map do |r|
|
53
|
+
next r if r.is_a?(String)
|
54
|
+
"\#{#{r.to_sass(@options)}}"
|
55
|
+
end.join
|
56
|
+
|
52
57
|
content = if @format == :sass
|
53
|
-
content =
|
58
|
+
content = value.gsub(/\*\/$/, '').rstrip
|
54
59
|
if content =~ /\A[ \t]/
|
55
60
|
# Re-indent SCSS comments like this:
|
56
61
|
# /* foo
|
@@ -66,7 +71,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
66
71
|
else
|
67
72
|
content.gsub!(/\n( \*|\/\/)/, "\n ")
|
68
73
|
spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
|
69
|
-
sep = node.silent ? "\n//" : "\n *"
|
74
|
+
sep = node.type == :silent ? "\n//" : "\n *"
|
70
75
|
if spaces >= 2
|
71
76
|
content.gsub(/\n /, sep)
|
72
77
|
else
|
@@ -74,25 +79,19 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
74
79
|
end
|
75
80
|
end
|
76
81
|
|
77
|
-
content.gsub!(/\A\/\*/, '//') if node.silent
|
82
|
+
content.gsub!(/\A\/\*/, '//') if node.type == :silent
|
78
83
|
content.gsub!(/^/, tab_str)
|
79
84
|
content.rstrip + "\n"
|
80
85
|
else
|
81
|
-
spaces = (' ' * [@tabs -
|
82
|
-
content = if node.silent
|
83
|
-
|
86
|
+
spaces = (' ' * [@tabs - value[/^ */].size, 0].max)
|
87
|
+
content = if node.type == :silent
|
88
|
+
value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
|
84
89
|
else
|
85
|
-
|
90
|
+
value
|
86
91
|
end.gsub(/^/, spaces) + "\n"
|
87
92
|
content
|
88
93
|
end
|
89
|
-
if node.loud
|
90
|
-
if node.silent
|
91
|
-
content.gsub!(%r{^\s*(//!?)}, '//!')
|
92
|
-
else
|
93
|
-
content.sub!(%r{^\s*(/\*)}, '/*!')
|
94
|
-
end
|
95
|
-
end
|
94
|
+
content.sub!(%r{^\s*(/\*)}, '/*!') if node.type == :loud
|
96
95
|
content
|
97
96
|
end
|
98
97
|
|
@@ -200,7 +199,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
200
199
|
elsif @format == :scss
|
201
200
|
name = selector_to_scss(node.rule)
|
202
201
|
res = name + yield
|
203
|
-
if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.silent
|
202
|
+
if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent
|
204
203
|
res.slice!(-3..-1)
|
205
204
|
res << "\n" << tab_str << "}\n"
|
206
205
|
end
|
@@ -11,6 +11,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
11
11
|
|
12
12
|
def initialize(env)
|
13
13
|
@environment = env
|
14
|
+
# Stack trace information, including mixin includes and imports.
|
15
|
+
@stack = []
|
14
16
|
end
|
15
17
|
|
16
18
|
# If an exception is raised, this adds proper metadata to the backtrace.
|
@@ -53,12 +55,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
53
55
|
# Removes this node from the tree if it's a silent comment.
|
54
56
|
def visit_comment(node)
|
55
57
|
return [] if node.invisible?
|
56
|
-
|
57
|
-
|
58
|
-
$1+Sass::Script.parse($2, node.line, 0, node.options).perform(@environment).to_s
|
59
|
-
end
|
60
|
-
node.value = run_interp([Sass::Script::String.new(node.value)])
|
61
|
-
end
|
58
|
+
node.resolved_value = run_interp_no_strip(node.value)
|
59
|
+
node.resolved_value.gsub!(/\\([\\#])/, '\1')
|
62
60
|
node
|
63
61
|
end
|
64
62
|
|
@@ -140,8 +138,9 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
140
138
|
return Sass::Tree::DirectiveNode.new("@import url(#{path})")
|
141
139
|
end
|
142
140
|
|
143
|
-
@
|
141
|
+
@stack.push(:filename => node.filename, :line => node.line)
|
144
142
|
root = node.imported_file.to_tree
|
143
|
+
Sass::Tree::Visitors::CheckNesting.visit(root)
|
145
144
|
node.children = root.children.map {|c| visit(c)}.flatten
|
146
145
|
node
|
147
146
|
rescue Sass::SyntaxError => e
|
@@ -149,7 +148,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
149
148
|
e.add_backtrace(:filename => node.filename, :line => node.line)
|
150
149
|
raise e
|
151
150
|
ensure
|
152
|
-
@
|
151
|
+
@stack.pop unless path
|
153
152
|
end
|
154
153
|
|
155
154
|
# Loads a mixin into the environment.
|
@@ -161,11 +160,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
161
160
|
|
162
161
|
# Runs a mixin.
|
163
162
|
def visit_mixin(node)
|
164
|
-
|
163
|
+
include_loop = true
|
164
|
+
handle_include_loop!(node) if @stack.any? {|e| e[:name] == node.name}
|
165
|
+
include_loop = false
|
165
166
|
|
166
|
-
|
167
|
-
original_env.push_frame(:filename => node.filename, :line => node.line)
|
168
|
-
original_env.prepare_frame(:mixin => node.name)
|
167
|
+
@stack.push(:filename => node.filename, :line => node.line, :name => node.name)
|
169
168
|
raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
|
170
169
|
|
171
170
|
if node.children.any? && !mixin.has_content
|
@@ -200,24 +199,25 @@ END
|
|
200
199
|
raise Sass::SyntaxError.new("Mixin #{node.name} is missing parameter #{var.inspect}.") unless env.var(var.name)
|
201
200
|
env
|
202
201
|
end
|
203
|
-
environment.caller = Sass::Environment.new(
|
202
|
+
environment.caller = Sass::Environment.new(@environment)
|
204
203
|
environment.content = node.children if node.has_children
|
205
204
|
|
206
205
|
trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
|
207
206
|
with_environment(environment) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
|
208
207
|
trace_node
|
209
208
|
rescue Sass::SyntaxError => e
|
210
|
-
|
209
|
+
unless include_loop
|
211
210
|
e.modify_backtrace(:mixin => node.name, :line => node.line)
|
212
211
|
e.add_backtrace(:line => node.line)
|
213
212
|
end
|
214
213
|
raise e
|
215
214
|
ensure
|
216
|
-
|
215
|
+
@stack.pop unless include_loop
|
217
216
|
end
|
218
217
|
|
219
218
|
def visit_content(node)
|
220
219
|
raise Sass::SyntaxError.new("No @content passed.") unless content = @environment.content
|
220
|
+
@stack.push(:filename => node.filename, :line => node.line, :name => '@content')
|
221
221
|
trace_node = Sass::Tree::TraceNode.from_node('@content', node)
|
222
222
|
with_environment(@environment.caller) {trace_node.children = content.map {|c| visit(c.dup)}.flatten}
|
223
223
|
trace_node
|
@@ -225,6 +225,8 @@ END
|
|
225
225
|
e.modify_backtrace(:mixin => '@content', :line => node.line)
|
226
226
|
e.add_backtrace(:line => node.line)
|
227
227
|
raise e
|
228
|
+
ensure
|
229
|
+
@stack.pop if content
|
228
230
|
end
|
229
231
|
|
230
232
|
# Runs any SassScript that may be embedded in a property.
|
@@ -246,9 +248,9 @@ END
|
|
246
248
|
parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
|
247
249
|
node.parsed_rules ||= parser.parse_selector
|
248
250
|
if node.options[:trace_selectors]
|
249
|
-
@
|
250
|
-
node.stack_trace =
|
251
|
-
@
|
251
|
+
@stack.push(:filename => node.filename, :line => node.line)
|
252
|
+
node.stack_trace = stack_trace
|
253
|
+
@stack.pop
|
252
254
|
end
|
253
255
|
yield
|
254
256
|
end
|
@@ -263,17 +265,17 @@ END
|
|
263
265
|
|
264
266
|
# Prints the expression to STDERR with a stylesheet trace.
|
265
267
|
def visit_warn(node)
|
266
|
-
@
|
268
|
+
@stack.push(:filename => node.filename, :line => node.line)
|
267
269
|
res = node.expr.perform(@environment)
|
268
270
|
res = res.value if res.is_a?(Sass::Script::String)
|
269
271
|
msg = "WARNING: #{res}\n "
|
270
|
-
msg <<
|
272
|
+
msg << stack_trace.join("\n ")
|
271
273
|
# JRuby doesn't automatically add a newline for #warn
|
272
274
|
msg << (RUBY_PLATFORM =~ /java/ ? "\n\n" : "\n")
|
273
275
|
Sass::Util.sass_warn msg
|
274
276
|
[]
|
275
277
|
ensure
|
276
|
-
@
|
278
|
+
@stack.pop
|
277
279
|
end
|
278
280
|
|
279
281
|
# Runs the child nodes until the continuation expression becomes false.
|
@@ -295,25 +297,52 @@ END
|
|
295
297
|
|
296
298
|
private
|
297
299
|
|
298
|
-
def
|
300
|
+
def stack_trace
|
301
|
+
trace = []
|
302
|
+
stack = @stack.map {|e| e.dup}.reverse
|
303
|
+
stack.each_cons(2) {|(e1, e2)| e1[:caller] = e2[:name]; [e1, e2]}
|
304
|
+
stack.each_with_index do |entry, i|
|
305
|
+
msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
|
306
|
+
msg << " of #{entry[:filename] || "an unknown file"}"
|
307
|
+
msg << ", in `#{entry[:caller]}'" if entry[:caller]
|
308
|
+
trace << msg
|
309
|
+
end
|
310
|
+
trace
|
311
|
+
end
|
312
|
+
|
313
|
+
def run_interp_no_strip(text)
|
299
314
|
text.map do |r|
|
300
315
|
next r if r.is_a?(String)
|
301
316
|
val = r.perform(@environment)
|
302
317
|
# Interpolated strings should never render with quotes
|
303
318
|
next val.value if val.is_a?(Sass::Script::String)
|
304
319
|
val.to_s
|
305
|
-
end.join
|
320
|
+
end.join
|
321
|
+
end
|
322
|
+
|
323
|
+
def run_interp(text)
|
324
|
+
run_interp_no_strip(text).strip
|
306
325
|
end
|
307
326
|
|
308
327
|
def handle_include_loop!(node)
|
309
328
|
msg = "An @include loop has been found:"
|
310
|
-
|
311
|
-
|
312
|
-
|
329
|
+
content_count = 0
|
330
|
+
mixins = @stack.reverse.map {|s| s[:name]}.compact.select do |s|
|
331
|
+
if s == '@content'
|
332
|
+
content_count += 1
|
333
|
+
false
|
334
|
+
elsif content_count > 0
|
335
|
+
content_count -= 1
|
336
|
+
false
|
337
|
+
else
|
338
|
+
true
|
339
|
+
end
|
313
340
|
end
|
314
341
|
|
315
|
-
|
316
|
-
msg
|
342
|
+
return if mixins.empty?
|
343
|
+
raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
|
344
|
+
|
345
|
+
msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
|
317
346
|
" #{m1} includes #{m2}"
|
318
347
|
end.join("\n")
|
319
348
|
raise Sass::SyntaxError.new(msg)
|
@@ -57,22 +57,12 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
57
57
|
|
58
58
|
def visit_comment(node)
|
59
59
|
return if node.invisible?
|
60
|
-
spaces = (' ' * [@tabs - node.
|
60
|
+
spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max)
|
61
61
|
|
62
|
-
content = node.
|
62
|
+
content = node.resolved_value.gsub(/^/, spaces).gsub(%r{^(\s*)//(.*)$}) do |md|
|
63
63
|
"#{$1}/*#{$2} */"
|
64
64
|
end
|
65
|
-
if
|
66
|
-
Sass::Util.sass_warn <<MESSAGE
|
67
|
-
WARNING:
|
68
|
-
On line #{node.line}#{" of '#{node.filename}'" if node.filename}
|
69
|
-
Comments will evaluate the contents of interpolations (\#{ ... }) in Sass 3.2.
|
70
|
-
Please escape the interpolation by adding a backslash before the hash sign.
|
71
|
-
MESSAGE
|
72
|
-
elsif content =~ /\\\#\{.*\}/
|
73
|
-
content.gsub!(/\\(\#\{.*\})/, '\1')
|
74
|
-
end
|
75
|
-
content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) && !node.loud
|
65
|
+
content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) && node.type != :loud
|
76
66
|
content
|
77
67
|
end
|
78
68
|
|
data/lib/sass/util.rb
CHANGED
@@ -612,6 +612,57 @@ MSG
|
|
612
612
|
'"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"'
|
613
613
|
end
|
614
614
|
|
615
|
+
# Extracts the non-string vlaues from an array containing both strings and non-strings.
|
616
|
+
# These values are replaced with escape sequences.
|
617
|
+
# This can be undone using \{#inject\_values}.
|
618
|
+
#
|
619
|
+
# This is useful e.g. when we want to do string manipulation
|
620
|
+
# on an interpolated string.
|
621
|
+
#
|
622
|
+
# The precise format of the resulting string is not guaranteed.
|
623
|
+
# However, it is guaranteed that newlines and whitespace won't be affected.
|
624
|
+
#
|
625
|
+
# @param arr [Array] The array from which values are extracted.
|
626
|
+
# @return [(String, Array)] The resulting string, and an array of extracted values.
|
627
|
+
def extract_values(arr)
|
628
|
+
values = []
|
629
|
+
return arr.map do |e|
|
630
|
+
next e.gsub('{', '{{') if e.is_a?(String)
|
631
|
+
values << e
|
632
|
+
next "{#{values.count - 1}}"
|
633
|
+
end.join, values
|
634
|
+
end
|
635
|
+
|
636
|
+
# Undoes \{#extract\_values} by transforming a string with escape sequences
|
637
|
+
# into an array of strings and non-string values.
|
638
|
+
#
|
639
|
+
# @param str [String] The string with escape sequences.
|
640
|
+
# @param values [Array] The array of values to inject.
|
641
|
+
# @return [Array] The array of strings and values.
|
642
|
+
def inject_values(str, values)
|
643
|
+
return [str.gsub('{{', '{')] if values.empty?
|
644
|
+
# Add an extra { so that we process the tail end of the string
|
645
|
+
result = (str + '{{').scan(/(.*?)(?:(\{\{)|\{(\d+)\})/m).map do |(pre, esc, n)|
|
646
|
+
[pre, esc ? '{' : '', n ? values[n.to_i] : '']
|
647
|
+
end.flatten(1)
|
648
|
+
result[-2] = '' # Get rid of the extra {
|
649
|
+
merge_adjacent_strings(result).reject {|s| s == ''}
|
650
|
+
end
|
651
|
+
|
652
|
+
# Allows modifications to be performed on the string form
|
653
|
+
# of an array containing both strings and non-strings.
|
654
|
+
#
|
655
|
+
# @param arr [Array] The array from which values are extracted.
|
656
|
+
# @yield [str] A block in which string manipulation can be done to the array.
|
657
|
+
# @yieldparam str [String] The string form of `arr`.
|
658
|
+
# @yieldreturn [String] The modified string.
|
659
|
+
# @return [Array] The modified, interpolated array.
|
660
|
+
def with_extracted_values(arr)
|
661
|
+
str, vals = extract_values(arr)
|
662
|
+
str = yield str
|
663
|
+
inject_values(str, vals)
|
664
|
+
end
|
665
|
+
|
615
666
|
## Static Method Stuff
|
616
667
|
|
617
668
|
# The context in which the ERB for \{#def\_static\_method} will be run.
|
data/test/Gemfile
ADDED
data/test/Gemfile.lock
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
less (1.2.21)
|
5
|
+
mutter (>= 0.4.2)
|
6
|
+
treetop (>= 1.4.2)
|
7
|
+
mutter (0.5.3)
|
8
|
+
polyglot (0.3.2)
|
9
|
+
rake (0.9.2)
|
10
|
+
treetop (1.4.10)
|
11
|
+
polyglot
|
12
|
+
polyglot (>= 0.3.1)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
less (< 2.0.0)
|
19
|
+
rake
|
@@ -1131,24 +1131,11 @@ div
|
|
1131
1131
|
SASS
|
1132
1132
|
end
|
1133
1133
|
|
1134
|
-
|
1134
|
+
def test_loud_comment_conversion
|
1135
1135
|
assert_renders(<<SASS, <<SCSS)
|
1136
1136
|
/*! \#{"interpolated"}
|
1137
|
-
/*!
|
1138
|
-
* \#{"also interpolated"}
|
1139
1137
|
SASS
|
1140
1138
|
/*! \#{"interpolated"} */
|
1141
|
-
/*!
|
1142
|
-
* \#{"also interpolated"} */
|
1143
|
-
SCSS
|
1144
|
-
assert_renders(<<SASS, <<SCSS)
|
1145
|
-
//! \#{"interpolated"}
|
1146
|
-
//!
|
1147
|
-
//! \#{"also interpolated"}
|
1148
|
-
SASS
|
1149
|
-
//! \#{"interpolated"}
|
1150
|
-
//!
|
1151
|
-
//! \#{"also interpolated"}
|
1152
1139
|
SCSS
|
1153
1140
|
end
|
1154
1141
|
|
data/test/sass/engine_test.rb
CHANGED
@@ -265,7 +265,7 @@ SASS
|
|
265
265
|
end
|
266
266
|
|
267
267
|
def test_imported_exception
|
268
|
-
[1, 2, 3, 4].each do |i|
|
268
|
+
[1, 2, 3, 4, 5].each do |i|
|
269
269
|
begin
|
270
270
|
Sass::Engine.new("@import bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
|
271
271
|
rescue Sass::SyntaxError => err
|
@@ -287,7 +287,7 @@ SASS
|
|
287
287
|
end
|
288
288
|
|
289
289
|
def test_double_imported_exception
|
290
|
-
[1, 2, 3, 4].each do |i|
|
290
|
+
[1, 2, 3, 4, 5].each do |i|
|
291
291
|
begin
|
292
292
|
Sass::Engine.new("@import nested_bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
|
293
293
|
rescue Sass::SyntaxError => err
|
@@ -1576,28 +1576,10 @@ foo
|
|
1576
1576
|
*/
|
1577
1577
|
SASS
|
1578
1578
|
end
|
1579
|
-
def test_loud_comment_in_silent_comment
|
1580
|
-
assert_equal <<CSS, render(<<SASS, :style => :compressed)
|
1581
|
-
foo{color:blue;/* foo */
|
1582
|
-
/* bar */
|
1583
|
-
/* */
|
1584
|
-
/* bip */
|
1585
|
-
/* baz */}
|
1586
|
-
CSS
|
1587
|
-
foo
|
1588
|
-
color: blue
|
1589
|
-
//! foo
|
1590
|
-
//! bar
|
1591
|
-
//!
|
1592
|
-
bip
|
1593
|
-
baz
|
1594
|
-
SASS
|
1595
|
-
end
|
1596
1579
|
|
1597
1580
|
def test_loud_comment_is_evaluated
|
1598
1581
|
assert_equal <<CSS, render(<<SASS)
|
1599
|
-
/*
|
1600
|
-
* Hue: 327.216deg */
|
1582
|
+
/* Hue: 327.216deg */
|
1601
1583
|
CSS
|
1602
1584
|
/*!
|
1603
1585
|
Hue: \#{hue(#f836a0)}
|
@@ -2036,6 +2018,31 @@ CSS
|
|
2036
2018
|
|
2037
2019
|
# Regression tests
|
2038
2020
|
|
2021
|
+
def test_interpolated_comment_in_mixin
|
2022
|
+
assert_equal <<CSS, render(<<SASS)
|
2023
|
+
/* color: red */
|
2024
|
+
.foo {
|
2025
|
+
color: red; }
|
2026
|
+
|
2027
|
+
/* color: blue */
|
2028
|
+
.foo {
|
2029
|
+
color: blue; }
|
2030
|
+
|
2031
|
+
/* color: green */
|
2032
|
+
.foo {
|
2033
|
+
color: green; }
|
2034
|
+
CSS
|
2035
|
+
=foo($var)
|
2036
|
+
/*! color: \#{$var}
|
2037
|
+
.foo
|
2038
|
+
color: $var
|
2039
|
+
|
2040
|
+
+foo(red)
|
2041
|
+
+foo(blue)
|
2042
|
+
+foo(green)
|
2043
|
+
SASS
|
2044
|
+
end
|
2045
|
+
|
2039
2046
|
def test_parens_in_mixins
|
2040
2047
|
assert_equal(<<CSS, render(<<SASS))
|
2041
2048
|
.foo {
|
@@ -2309,15 +2316,6 @@ CSS
|
|
2309
2316
|
SASS
|
2310
2317
|
end
|
2311
2318
|
|
2312
|
-
def test_comment_interpolation_warning
|
2313
|
-
assert_warning(<<END) {render("/* \#{foo}")}
|
2314
|
-
WARNING:
|
2315
|
-
On line 1 of 'test_comment_interpolation_warning_inline.sass'
|
2316
|
-
Comments will evaluate the contents of interpolations (\#{ ... }) in Sass 3.2.
|
2317
|
-
Please escape the interpolation by adding a backslash before the hash sign.
|
2318
|
-
END
|
2319
|
-
end
|
2320
|
-
|
2321
2319
|
def test_loud_comment_interpolations_can_be_escaped
|
2322
2320
|
assert_equal <<CSS, render(<<SASS)
|
2323
2321
|
/* \#{foo} */
|
data/test/sass/plugin_test.rb
CHANGED
@@ -8,7 +8,7 @@ class SassPluginTest < Test::Unit::TestCase
|
|
8
8
|
@@templates = %w{
|
9
9
|
complex script parent_ref import scss_import alt
|
10
10
|
subdir/subdir subdir/nested_subdir/nested_subdir
|
11
|
-
options
|
11
|
+
options import_content
|
12
12
|
}
|
13
13
|
@@templates += %w[import_charset import_charset_ibm866] unless Sass::Util.ruby1_8?
|
14
14
|
@@templates << 'import_charset_1_8' if Sass::Util.ruby1_8?
|
@@ -0,0 +1 @@
|
|
1
|
+
a { b: c; }
|
data/test/sass/scss/css_test.rb
CHANGED
@@ -802,6 +802,12 @@ SCSS
|
|
802
802
|
assert_selector_parses('E*:hover')
|
803
803
|
end
|
804
804
|
|
805
|
+
def test_spaceless_combo_selectors
|
806
|
+
assert_equal "E > F {\n a: b; }\n", render("E>F { a: b;} ")
|
807
|
+
assert_equal "E ~ F {\n a: b; }\n", render("E~F { a: b;} ")
|
808
|
+
assert_equal "E + F {\n a: b; }\n", render("E+F { a: b;} ")
|
809
|
+
end
|
810
|
+
|
805
811
|
## Errors
|
806
812
|
|
807
813
|
def test_invalid_directives
|
data/test/sass/util_test.rb
CHANGED
@@ -208,6 +208,18 @@ class UtilTest < Test::Unit::TestCase
|
|
208
208
|
assert(set_eql?(s1, s2))
|
209
209
|
end
|
210
210
|
|
211
|
+
def test_extract_and_inject_values
|
212
|
+
test = lambda {|arr| assert_equal(arr, with_extracted_values(arr) {|str| str})}
|
213
|
+
|
214
|
+
test[['foo bar']]
|
215
|
+
test[['foo {12} bar']]
|
216
|
+
test[['foo {{12} bar']]
|
217
|
+
test[['foo {{1', 12, '2} bar']]
|
218
|
+
test[['foo 1', 2, '{3', 4, 5, 6, '{7}', 8]]
|
219
|
+
test[['foo 1', [2, 3, 4], ' bar']]
|
220
|
+
test[['foo ', 1, "\n bar\n", [2, 3, 4], "\n baz"]]
|
221
|
+
end
|
222
|
+
|
211
223
|
def test_caller_info
|
212
224
|
assert_equal(["/tmp/foo.rb", 12, "fizzle"], caller_info("/tmp/foo.rb:12: in `fizzle'"))
|
213
225
|
assert_equal(["/tmp/foo.rb", 12, nil], caller_info("/tmp/foo.rb:12"))
|
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.2.0.alpha.
|
4
|
+
version: 3.2.0.alpha.33
|
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: 2011-
|
14
|
+
date: 2011-10-06 00:00:00 -04:00
|
15
15
|
default_executable:
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
@@ -229,6 +229,7 @@ files:
|
|
229
229
|
- test/sass/results/units.css
|
230
230
|
- test/sass/results/warn.css
|
231
231
|
- test/sass/results/warn_imported.css
|
232
|
+
- test/sass/results/import_content.css
|
232
233
|
- test/sass/script_conversion_test.rb
|
233
234
|
- test/sass/script_test.rb
|
234
235
|
- test/sass/scss/css_test.rb
|
@@ -277,10 +278,16 @@ files:
|
|
277
278
|
- test/sass/templates/units.sass
|
278
279
|
- test/sass/templates/warn.sass
|
279
280
|
- test/sass/templates/warn_imported.sass
|
281
|
+
- test/sass/templates/_imported_content.sass
|
282
|
+
- test/sass/templates/bork5.sass
|
283
|
+
- test/sass/templates/import_content.sass
|
284
|
+
- test/sass/templates/nested_bork5.sass
|
280
285
|
- test/sass/test_helper.rb
|
281
286
|
- test/sass/util/subset_map_test.rb
|
282
287
|
- test/sass/util_test.rb
|
283
288
|
- test/test_helper.rb
|
289
|
+
- test/Gemfile
|
290
|
+
- test/Gemfile.lock
|
284
291
|
- extra/update_watch.rb
|
285
292
|
- Rakefile
|
286
293
|
- init.rb
|