sass 3.2.0.alpha.21 → 3.2.0.alpha.33
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/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
|