sass 3.2.0.alpha.35 → 3.2.0.alpha.49
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -1
- data/REVISION +1 -1
- data/VERSION +1 -1
- data/lib/sass.rb +22 -0
- data/lib/sass/engine.rb +2 -2
- data/lib/sass/environment.rb +0 -1
- data/lib/sass/exec.rb +1 -1
- data/lib/sass/importers/filesystem.rb +1 -1
- data/lib/sass/plugin.rb +4 -8
- data/lib/sass/plugin/compiler.rb +42 -17
- data/lib/sass/plugin/configuration.rb +0 -2
- data/lib/sass/repl.rb +0 -1
- data/lib/sass/script/funcall.rb +6 -1
- data/lib/sass/script/functions.rb +2 -2
- data/lib/sass/script/number.rb +1 -1
- data/lib/sass/script/parser.rb +12 -5
- data/lib/sass/script/variable.rb +0 -1
- data/lib/sass/scss/css_parser.rb +1 -0
- data/lib/sass/scss/parser.rb +37 -12
- data/lib/sass/scss/rx.rb +8 -3
- data/lib/sass/selector.rb +21 -0
- data/lib/sass/selector/abstract_sequence.rb +7 -0
- data/lib/sass/tree/media_node.rb +4 -4
- data/lib/sass/tree/rule_node.rb +5 -0
- data/lib/sass/tree/visitors/check_nesting.rb +10 -10
- data/lib/sass/tree/visitors/convert.rb +3 -3
- data/lib/sass/tree/visitors/cssize.rb +5 -1
- data/lib/sass/tree/visitors/perform.rb +16 -2
- data/lib/sass/tree/visitors/to_css.rb +2 -1
- data/lib/sass/util.rb +4 -1
- data/test/sass/conversion_test.rb +32 -2
- data/test/sass/engine_test.rb +105 -1
- data/test/sass/extend_test.rb +65 -0
- data/test/sass/importer_test.rb +7 -0
- data/test/sass/plugin_test.rb +16 -13
- data/test/sass/script_conversion_test.rb +2 -0
- data/test/sass/script_test.rb +18 -0
- data/test/sass/scss/scss_test.rb +34 -0
- data/test/sass/test_helper.rb +1 -1
- data/test/test_helper.rb +1 -0
- metadata +3 -3
data/lib/sass/scss/rx.rb
CHANGED
@@ -112,6 +112,7 @@ module Sass
|
|
112
112
|
INTERP_START = /#\{/
|
113
113
|
ANY = /:(-[-\w]+-)?any\(/i
|
114
114
|
|
115
|
+
IDENT_HYPHEN_INTERP = /-(#\{)/
|
115
116
|
STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
|
116
117
|
STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\'/
|
117
118
|
STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
|
@@ -119,9 +120,13 @@ module Sass
|
|
119
120
|
# We could use it for 1.9 only, but I don't want to introduce a cross-version
|
120
121
|
# behavior difference.
|
121
122
|
# In any case, almost all CSS idents will be matched by this.
|
122
|
-
|
123
|
-
|
124
|
-
|
123
|
+
#
|
124
|
+
# We explicitly avoid parsing newlines or values/selectors longer than
|
125
|
+
# about 50 characters. This mitigates the problem of exponential parsing
|
126
|
+
# time when a value has a long string of valid, parsable content followed
|
127
|
+
# by something invalid.
|
128
|
+
STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){0,50}([;}])/i
|
129
|
+
STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){0,50}([{])/i
|
125
130
|
end
|
126
131
|
end
|
127
132
|
end
|
data/lib/sass/selector.rb
CHANGED
@@ -81,6 +81,27 @@ module Sass
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
# A placeholder selector (e.g. `%foo`).
|
85
|
+
# This exists to be replaced via `@extend`.
|
86
|
+
# Rulesets using this selector will not be printed, but can be extended.
|
87
|
+
# Otherwise, this acts just like a class selector.
|
88
|
+
class Placeholder < Simple
|
89
|
+
# The placeholder name.
|
90
|
+
#
|
91
|
+
# @return [Array<String, Sass::Script::Node>]
|
92
|
+
attr_reader :name
|
93
|
+
|
94
|
+
# @param name [Array<String, Sass::Script::Node>] The placeholder name
|
95
|
+
def initialize(name)
|
96
|
+
@name = name
|
97
|
+
end
|
98
|
+
|
99
|
+
# @see Selector#to_a
|
100
|
+
def to_a
|
101
|
+
["%", *@name]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
84
105
|
# A universal selector (`*` in CSS).
|
85
106
|
class Universal < Simple
|
86
107
|
# The selector namespace.
|
@@ -57,6 +57,13 @@ module Sass
|
|
57
57
|
other.class == self.class && other.hash == self.hash && _eql?(other)
|
58
58
|
end
|
59
59
|
alias_method :==, :eql?
|
60
|
+
|
61
|
+
# Whether or not this selector sequence contains a placeholder selector.
|
62
|
+
# Checks recursively.
|
63
|
+
def has_placeholder?
|
64
|
+
@has_placeholder ||=
|
65
|
+
members.any? {|m| m.is_a?(AbstractSequence) ? m.has_placeholder? : m.is_a?(Placeholder)}
|
66
|
+
end
|
60
67
|
end
|
61
68
|
end
|
62
69
|
end
|
data/lib/sass/tree/media_node.rb
CHANGED
@@ -6,9 +6,9 @@ module Sass::Tree
|
|
6
6
|
#
|
7
7
|
# @see Sass::Tree
|
8
8
|
class MediaNode < DirectiveNode
|
9
|
-
# The media query (e.g. `print` or `screen`).
|
9
|
+
# The media query. A list of comma-separated queries (e.g. `print` or `screen`).
|
10
10
|
#
|
11
|
-
# @return [String]
|
11
|
+
# @return [Array<String>]
|
12
12
|
attr_accessor :query
|
13
13
|
|
14
14
|
# @see RuleNode#tabs
|
@@ -17,7 +17,7 @@ module Sass::Tree
|
|
17
17
|
# @see RuleNode#group_end
|
18
18
|
attr_accessor :group_end
|
19
19
|
|
20
|
-
# @param query [String] See \{#query}
|
20
|
+
# @param query [Array<String>] See \{#query}
|
21
21
|
def initialize(query)
|
22
22
|
@query = query
|
23
23
|
@tabs = 0
|
@@ -26,7 +26,7 @@ module Sass::Tree
|
|
26
26
|
|
27
27
|
# @see DirectiveNode#value
|
28
28
|
def value
|
29
|
-
"@media #{query}"
|
29
|
+
"@media #{query.join(', ')}"
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
data/lib/sass/tree/rule_node.rb
CHANGED
@@ -122,6 +122,11 @@ module Sass::Tree
|
|
122
122
|
:line => self.line}
|
123
123
|
end
|
124
124
|
|
125
|
+
# A rule node is invisible if it has only placeholder selectors.
|
126
|
+
def invisible?
|
127
|
+
resolved_rules.members.all? {|seq| seq.has_placeholder?}
|
128
|
+
end
|
129
|
+
|
125
130
|
private
|
126
131
|
|
127
132
|
def try_to_parse_non_interpolated_rules
|
@@ -17,11 +17,12 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
|
|
17
17
|
raise e
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
CONTROL_NODES = [Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
|
21
|
+
Sass::Tree::WhileNode, Sass::Tree::TraceNode]
|
22
|
+
SCRIPT_NODES = [Sass::Tree::ImportNode] + CONTROL_NODES
|
22
23
|
def visit_children(parent)
|
23
24
|
old_parent = @parent
|
24
|
-
@parent = parent unless is_any_of?(parent,
|
25
|
+
@parent = parent unless is_any_of?(parent, SCRIPT_NODES)
|
25
26
|
old_real_parent, @real_parent = @real_parent, parent
|
26
27
|
super
|
27
28
|
ensure
|
@@ -76,22 +77,21 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
|
|
76
77
|
end
|
77
78
|
|
78
79
|
VALID_FUNCTION_CHILDREN = [
|
79
|
-
Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::
|
80
|
-
Sass::Tree::
|
81
|
-
|
82
|
-
]
|
80
|
+
Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
|
81
|
+
Sass::Tree::VariableNode, Sass::Tree::WarnNode
|
82
|
+
] + CONTROL_NODES
|
83
83
|
def invalid_function_child?(parent, child)
|
84
84
|
unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
|
85
85
|
"Functions can only contain variable declarations and control directives."
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
|
89
|
+
VALID_IMPORT_PARENTS = [
|
90
90
|
Sass::Tree::IfNode, Sass::Tree::ForNode, Sass::Tree::WhileNode,
|
91
91
|
Sass::Tree::EachNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode
|
92
92
|
]
|
93
93
|
def invalid_import_parent?(parent, child)
|
94
|
-
if is_any_of?(@real_parent,
|
94
|
+
if is_any_of?(@real_parent, VALID_IMPORT_PARENTS)
|
95
95
|
return "Import directives may not be used within control directives or mixins."
|
96
96
|
end
|
97
97
|
return if parent.is_a?(Sass::Tree::RootNode)
|
@@ -114,7 +114,7 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
|
|
114
114
|
"Mixins may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
|
115
115
|
end
|
116
116
|
|
117
|
-
VALID_PROP_CHILDREN = [Sass::Tree::CommentNode, Sass::Tree::PropNode]
|
117
|
+
VALID_PROP_CHILDREN = [Sass::Tree::CommentNode, Sass::Tree::PropNode, Sass::Tree::MixinNode] + CONTROL_NODES
|
118
118
|
def invalid_prop_child?(parent, child)
|
119
119
|
unless is_any_of?(child, VALID_PROP_CHILDREN)
|
120
120
|
"Illegal nesting: Only properties may be nested beneath properties."
|
@@ -148,7 +148,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
148
148
|
end
|
149
149
|
|
150
150
|
def visit_media(node)
|
151
|
-
"#{tab_str}@media #{node.query}#{yield}"
|
151
|
+
"#{tab_str}@media #{node.query.join(', ')}#{yield}"
|
152
152
|
end
|
153
153
|
|
154
154
|
def visit_mixindef(node)
|
@@ -228,7 +228,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
228
228
|
def selector_to_sass(sel)
|
229
229
|
sel.map do |r|
|
230
230
|
if r.is_a?(String)
|
231
|
-
r.gsub(/(,[ \t]*)
|
231
|
+
r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
|
232
232
|
else
|
233
233
|
"\#{#{r.to_sass(@options)}}"
|
234
234
|
end
|
@@ -237,7 +237,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
237
237
|
|
238
238
|
def selector_to_scss(sel)
|
239
239
|
sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
|
240
|
-
join.gsub(/^[ \t]*/, tab_str)
|
240
|
+
join.gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
|
241
241
|
end
|
242
242
|
|
243
243
|
def semi
|
@@ -76,6 +76,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
|
|
76
76
|
sseq = seq.members.first
|
77
77
|
if !sseq.is_a?(Sass::Selector::SimpleSequence)
|
78
78
|
raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
|
79
|
+
elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
|
80
|
+
raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
|
79
81
|
end
|
80
82
|
|
81
83
|
sel = sseq.members
|
@@ -117,7 +119,9 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
|
|
117
119
|
|
118
120
|
media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
|
119
121
|
node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
|
120
|
-
media.each
|
122
|
+
media.each do |n|
|
123
|
+
n.query = node.query.map {|pq| n.query.map {|cq| "#{pq} and #{cq}"}}.flatten
|
124
|
+
end
|
121
125
|
(node.children.empty? ? [] : [node]) + media
|
122
126
|
end
|
123
127
|
|
@@ -87,7 +87,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
87
87
|
# Runs SassScript interpolation in the selector,
|
88
88
|
# and then parses the result into a {Sass::Selector::CommaSequence}.
|
89
89
|
def visit_extend(node)
|
90
|
-
parser = Sass::SCSS::
|
90
|
+
parser = Sass::SCSS::StaticParser.new(run_interp(node.selector), node.filename, node.line)
|
91
91
|
node.resolved_selector = parser.parse_selector
|
92
92
|
node
|
93
93
|
end
|
@@ -137,9 +137,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
137
137
|
if path = node.css_import?
|
138
138
|
return Sass::Tree::DirectiveNode.new("@import url(#{path})")
|
139
139
|
end
|
140
|
+
file = node.imported_file
|
141
|
+
handle_import_loop!(node) if @stack.any? {|e| e[:filename] == file.options[:filename]}
|
140
142
|
|
141
143
|
@stack.push(:filename => node.filename, :line => node.line)
|
142
|
-
root =
|
144
|
+
root = file.to_tree
|
143
145
|
Sass::Tree::Visitors::CheckNesting.visit(root)
|
144
146
|
node.children = root.children.map {|c| visit(c)}.flatten
|
145
147
|
node
|
@@ -347,4 +349,16 @@ END
|
|
347
349
|
end.join("\n")
|
348
350
|
raise Sass::SyntaxError.new(msg)
|
349
351
|
end
|
352
|
+
|
353
|
+
def handle_import_loop!(node)
|
354
|
+
msg = "An @import loop has been found:"
|
355
|
+
files = @stack.map {|s| s[:filename]}.compact
|
356
|
+
raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself") if files.size == 1
|
357
|
+
|
358
|
+
files << node.filename << node.imported_file.options[:filename]
|
359
|
+
msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
|
360
|
+
" #{m1} imports #{m2}"
|
361
|
+
end.join("\n")
|
362
|
+
raise Sass::SyntaxError.new(msg)
|
363
|
+
end
|
350
364
|
end
|
@@ -131,10 +131,11 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
131
131
|
per_rule_indent, total_indent = [:nested, :expanded].include?(node.style) ? [rule_indent, ''] : ['', rule_indent]
|
132
132
|
|
133
133
|
joined_rules = node.resolved_rules.members.map do |seq|
|
134
|
+
next if seq.has_placeholder?
|
134
135
|
rule_part = seq.to_a.join
|
135
136
|
rule_part.gsub!(/\s*([^,])\s*\n\s*/m, '\1 ') if node.style == :compressed
|
136
137
|
rule_part
|
137
|
-
end.join(rule_separator)
|
138
|
+
end.compact.join(rule_separator)
|
138
139
|
|
139
140
|
joined_rules.sub!(/\A\s*/, per_rule_indent)
|
140
141
|
joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
|
data/lib/sass/util.rb
CHANGED
@@ -467,7 +467,8 @@ MSG
|
|
467
467
|
# We allow any printable ASCII characters but double quotes in the charset decl
|
468
468
|
bin = str.dup.force_encoding("BINARY")
|
469
469
|
encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
|
470
|
-
|
470
|
+
re = Sass::Util::CHARSET_REGEXPS[enc]
|
471
|
+
re && bin =~ re
|
471
472
|
end
|
472
473
|
charset, bom = $1, $2
|
473
474
|
if charset
|
@@ -508,6 +509,8 @@ MSG
|
|
508
509
|
Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
|
509
510
|
_enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
|
510
511
|
_enc("\uFEFF", e)})/)
|
512
|
+
rescue Encoding::ConverterNotFound => _
|
513
|
+
nil # JRuby on Java 5 doesn't support UTF-32
|
511
514
|
rescue
|
512
515
|
# /\A@charset "(.*?)"/
|
513
516
|
Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
|
@@ -180,7 +180,7 @@ SCSS
|
|
180
180
|
def test_multiline_properties
|
181
181
|
assert_scss_to_sass <<SASS, <<SCSS
|
182
182
|
foo bar
|
183
|
-
baz: bip
|
183
|
+
baz: bip bam boon
|
184
184
|
SASS
|
185
185
|
foo bar {
|
186
186
|
baz:
|
@@ -191,7 +191,7 @@ SCSS
|
|
191
191
|
|
192
192
|
assert_scss_to_scss <<OUT, <<IN
|
193
193
|
foo bar {
|
194
|
-
baz: bip
|
194
|
+
baz: bip bam boon; }
|
195
195
|
OUT
|
196
196
|
foo bar {
|
197
197
|
baz:
|
@@ -1177,6 +1177,36 @@ SCSS
|
|
1177
1177
|
SCSS
|
1178
1178
|
end
|
1179
1179
|
|
1180
|
+
def test_placeholder_conversion
|
1181
|
+
assert_renders(<<SASS, <<SCSS)
|
1182
|
+
#content a%foo.bar
|
1183
|
+
color: blue
|
1184
|
+
SASS
|
1185
|
+
#content a%foo.bar {
|
1186
|
+
color: blue; }
|
1187
|
+
SCSS
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
def test_placeholder_interoplation_conversion
|
1191
|
+
assert_renders(<<SASS, <<SCSS)
|
1192
|
+
$foo: foo
|
1193
|
+
|
1194
|
+
%\#{$foo}
|
1195
|
+
color: blue
|
1196
|
+
|
1197
|
+
.bar
|
1198
|
+
@extend %foo
|
1199
|
+
SASS
|
1200
|
+
$foo: foo;
|
1201
|
+
|
1202
|
+
%\#{$foo} {
|
1203
|
+
color: blue; }
|
1204
|
+
|
1205
|
+
.bar {
|
1206
|
+
@extend %foo; }
|
1207
|
+
SCSS
|
1208
|
+
end
|
1209
|
+
|
1180
1210
|
private
|
1181
1211
|
|
1182
1212
|
def assert_sass_to_sass(sass, options = {})
|
data/test/sass/engine_test.rb
CHANGED
@@ -39,6 +39,9 @@ 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\n @extend .foo .bar" => "Can't extend .foo .bar: can't extend nested selectors",
|
43
|
+
"a\n @extend >" => "Can't extend >: invalid selector",
|
44
|
+
"a\n @extend &.foo" => "Can't extend &.foo: can't extend parent selectors",
|
42
45
|
"a: b" => 'Properties are only allowed within rules, directives, mixin includes, or other properties.',
|
43
46
|
":a b" => 'Properties are only allowed within rules, directives, mixin includes, or other properties.',
|
44
47
|
"$" => 'Invalid variable: "$".',
|
@@ -64,7 +67,7 @@ MSG
|
|
64
67
|
"@mixin foo\n @import foo" => "Import directives may not be used within control directives or mixins.",
|
65
68
|
"@import foo;" => "Invalid @import: expected end of line, was \";\".",
|
66
69
|
'$foo: "bar" "baz" !' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "!"},
|
67
|
-
'$foo: "bar" "baz" $' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "$"},
|
70
|
+
'$foo: "bar" "baz" $' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "$"}, #'
|
68
71
|
"=foo\n :color red\n.bar\n +bang" => "Undefined mixin 'bang'.",
|
69
72
|
"=foo\n :color red\n.bar\n +bang_bop" => "Undefined mixin 'bang_bop'.",
|
70
73
|
"=foo\n :color red\n.bar\n +bang-bop" => "Undefined mixin 'bang-bop'.",
|
@@ -141,6 +144,7 @@ MSG
|
|
141
144
|
"@mixin foo\n @extend .bar\n@include foo" => ["Extend directives may only be used within rules.", 2],
|
142
145
|
"foo\n &a\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"a\"\n\n\"a\" may only be used at the beginning of a selector.", 2],
|
143
146
|
"foo\n &1\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"1\"\n\n\"1\" may only be used at the beginning of a selector.", 2],
|
147
|
+
"foo %\n a: b" => ['Invalid CSS after "foo %": expected placeholder name, was ""', 1],
|
144
148
|
"=foo\n @content error" => "Invalid content directive. Trailing characters found: \"error\".",
|
145
149
|
"=foo\n @content\n b: c" => "Illegal nesting: Nothing may be nested beneath @content directives.",
|
146
150
|
"@content" => '@content may only be used within a mixin.',
|
@@ -476,6 +480,53 @@ MESSAGE
|
|
476
480
|
assert_hash_has(err.sass_backtrace[2], :mixin => "foo", :line => 2)
|
477
481
|
end
|
478
482
|
|
483
|
+
def test_basic_import_loop_exception
|
484
|
+
import = filename_for_test
|
485
|
+
importer = MockImporter.new
|
486
|
+
importer.add_import(import, "@import '#{import}'")
|
487
|
+
|
488
|
+
engine = Sass::Engine.new("@import '#{import}'", :filename => import,
|
489
|
+
:load_paths => [importer])
|
490
|
+
|
491
|
+
assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
|
492
|
+
An @import loop has been found: #{import} imports itself
|
493
|
+
ERR
|
494
|
+
end
|
495
|
+
|
496
|
+
def test_double_import_loop_exception
|
497
|
+
importer = MockImporter.new
|
498
|
+
importer.add_import("foo", "@import 'bar'")
|
499
|
+
importer.add_import("bar", "@import 'foo'")
|
500
|
+
|
501
|
+
engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
|
502
|
+
:load_paths => [importer])
|
503
|
+
|
504
|
+
assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
|
505
|
+
An @import loop has been found:
|
506
|
+
#{filename_for_test} imports foo
|
507
|
+
foo imports bar
|
508
|
+
bar imports foo
|
509
|
+
ERR
|
510
|
+
end
|
511
|
+
|
512
|
+
def test_deep_import_loop_exception
|
513
|
+
importer = MockImporter.new
|
514
|
+
importer.add_import("foo", "@import 'bar'")
|
515
|
+
importer.add_import("bar", "@import 'baz'")
|
516
|
+
importer.add_import("baz", "@import 'foo'")
|
517
|
+
|
518
|
+
engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
|
519
|
+
:load_paths => [importer])
|
520
|
+
|
521
|
+
assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
|
522
|
+
An @import loop has been found:
|
523
|
+
#{filename_for_test} imports foo
|
524
|
+
foo imports bar
|
525
|
+
bar imports baz
|
526
|
+
baz imports foo
|
527
|
+
ERR
|
528
|
+
end
|
529
|
+
|
479
530
|
def test_exception_css_with_offset
|
480
531
|
opts = {:full_exception => true, :line => 362}
|
481
532
|
render(("a\n b: c\n" * 10) + "d\n e:\n" + ("f\n g: h\n" * 10), opts)
|
@@ -598,6 +649,16 @@ CSS
|
|
598
649
|
assert File.exists?(sassc_file)
|
599
650
|
end
|
600
651
|
|
652
|
+
def test_import_from_global_load_paths
|
653
|
+
importer = MockImporter.new
|
654
|
+
importer.add_import("imported", "div{color:red}")
|
655
|
+
Sass.load_paths << importer
|
656
|
+
|
657
|
+
assert_equal "div {\n color: red; }\n", Sass::Engine.new('@import "imported"').render
|
658
|
+
ensure
|
659
|
+
Sass.load_paths.clear
|
660
|
+
end
|
661
|
+
|
601
662
|
def test_nonexistent_import
|
602
663
|
assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) do
|
603
664
|
File to import not found or unreadable: nonexistent.sass.
|
@@ -1209,6 +1270,18 @@ bar
|
|
1209
1270
|
SASS
|
1210
1271
|
end
|
1211
1272
|
|
1273
|
+
def test_control_directive_in_nested_property
|
1274
|
+
assert_equal(<<CSS, render(<<SASS))
|
1275
|
+
foo {
|
1276
|
+
a-b: c; }
|
1277
|
+
CSS
|
1278
|
+
foo
|
1279
|
+
a:
|
1280
|
+
@if true
|
1281
|
+
b: c
|
1282
|
+
SASS
|
1283
|
+
end
|
1284
|
+
|
1212
1285
|
def test_interpolation
|
1213
1286
|
assert_equal("a-1 {\n b-2-3: c-3; }\n", render(<<SASS))
|
1214
1287
|
$a: 1
|
@@ -1956,6 +2029,19 @@ CSS
|
|
1956
2029
|
SASS
|
1957
2030
|
end
|
1958
2031
|
|
2032
|
+
def test_double_media_bubbling_with_commas
|
2033
|
+
assert_equal <<CSS, render(<<SASS)
|
2034
|
+
@media foo and baz, foo and bang, bar and baz, bar and bang {
|
2035
|
+
.foo {
|
2036
|
+
c: d; } }
|
2037
|
+
CSS
|
2038
|
+
@media foo, bar
|
2039
|
+
@media baz, bang
|
2040
|
+
.foo
|
2041
|
+
c: d
|
2042
|
+
SASS
|
2043
|
+
end
|
2044
|
+
|
1959
2045
|
def test_rule_media_rule_bubbling
|
1960
2046
|
assert_equal <<CSS, render(<<SASS)
|
1961
2047
|
@media bar {
|
@@ -2018,6 +2104,24 @@ CSS
|
|
2018
2104
|
|
2019
2105
|
# Regression tests
|
2020
2106
|
|
2107
|
+
def test_tricky_mixin_loop_exception
|
2108
|
+
render <<SASS
|
2109
|
+
@mixin foo($a)
|
2110
|
+
@if $a
|
2111
|
+
@include foo(false)
|
2112
|
+
@include foo(true)
|
2113
|
+
@else
|
2114
|
+
a: b
|
2115
|
+
|
2116
|
+
a
|
2117
|
+
@include foo(true)
|
2118
|
+
SASS
|
2119
|
+
assert(false, "Exception not raised")
|
2120
|
+
rescue Sass::SyntaxError => err
|
2121
|
+
assert_equal("An @include loop has been found: foo includes itself", err.message)
|
2122
|
+
assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
|
2123
|
+
end
|
2124
|
+
|
2021
2125
|
def test_interpolated_comment_in_mixin
|
2022
2126
|
assert_equal <<CSS, render(<<SASS)
|
2023
2127
|
/* color: red */
|