sass 3.2.0.alpha.35 → 3.2.0.alpha.49
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/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 */
|