mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
[Sass] [SCSS] Support interpolation in SCSS selectors.
This commit is contained in:
parent
d7bbab7a24
commit
49f1fef36b
7 changed files with 166 additions and 57 deletions
|
@ -134,6 +134,18 @@ module Haml
|
|||
end
|
||||
end
|
||||
|
||||
# Destructively strips whitespace from the beginning and end
|
||||
# of the first and last elements, respectively,
|
||||
# in the array (if those elements are strings).
|
||||
#
|
||||
# @param arr [Array]
|
||||
# @return [Array] `arr`
|
||||
def strip_string_array(arr)
|
||||
arr.first.lstrip! if arr.first.is_a?(String)
|
||||
arr.last.rstrip! if arr.last.is_a?(String)
|
||||
arr
|
||||
end
|
||||
|
||||
# Returns information about the caller of the previous method.
|
||||
#
|
||||
# @param entry [String] An entry in the `#caller` list, or a similarly formatted string
|
||||
|
|
|
@ -195,15 +195,16 @@ module Sass
|
|||
end
|
||||
|
||||
def ruleset
|
||||
rules = str do
|
||||
return unless selector
|
||||
rules = []
|
||||
return unless v = selector
|
||||
rules.concat v
|
||||
|
||||
while tok(/,/)
|
||||
ss; expr!(:selector)
|
||||
end
|
||||
while tok(/,/)
|
||||
rules << ',' << str {ss}
|
||||
rules.concat expr!(:selector)
|
||||
end
|
||||
|
||||
block(node(Sass::Tree::RuleNode.new([rules.strip])))
|
||||
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)))
|
||||
end
|
||||
|
||||
def block(node)
|
||||
|
@ -271,9 +272,14 @@ module Sass
|
|||
|
||||
def selector
|
||||
# The combinator here allows the "> E" hack
|
||||
return unless combinator || simple_selector_sequence
|
||||
simple_selector_sequence while combinator
|
||||
true
|
||||
return unless (comb = combinator) || (seq = simple_selector_sequence)
|
||||
res = [comb] + (seq || [])
|
||||
|
||||
while v = combinator
|
||||
res << v
|
||||
res.concat(simple_selector_sequence || [])
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def combinator
|
||||
|
@ -281,47 +287,47 @@ module Sass
|
|||
end
|
||||
|
||||
def simple_selector_sequence
|
||||
unless element_name || tok(HASH) || class_expr ||
|
||||
attrib || negation || pseudo || tok(/&/)
|
||||
# This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
|
||||
return expr
|
||||
end
|
||||
# This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
|
||||
return expr unless e = element_name || tok(HASH) || class_expr ||
|
||||
attrib || negation || pseudo || tok(/&/) || interpolation
|
||||
res = [e]
|
||||
|
||||
# The tok(/\*/) allows the "E*" hack
|
||||
nil while tok(HASH) || class_expr || attrib ||
|
||||
negation || pseudo || tok(/\*/)
|
||||
true
|
||||
end
|
||||
|
||||
def class_expr
|
||||
return unless tok(/\./)
|
||||
tok! IDENT
|
||||
end
|
||||
|
||||
def element_name
|
||||
res = tok(IDENT) || tok(/\*/)
|
||||
if tok(/\|/)
|
||||
@expected = "element name or *"
|
||||
res = tok(IDENT) || tok!(/\*/)
|
||||
while v = element_name || tok(HASH) || class_expr ||
|
||||
attrib || negation || pseudo || tok(/\*/) || interpolation
|
||||
res << v
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def class_expr
|
||||
return unless tok(/\./)
|
||||
'.' + tok!(IDENT)
|
||||
end
|
||||
|
||||
def element_name
|
||||
return unless name = tok(IDENT) || tok(/\*/) || tok?(/\|/)
|
||||
if tok(/\|/)
|
||||
@expected = "element name or *"
|
||||
name << "|" << (tok(IDENT) || tok!(/\*/))
|
||||
end
|
||||
name
|
||||
end
|
||||
|
||||
def attrib
|
||||
return unless tok(/\[/)
|
||||
ss
|
||||
attrib_name!; ss
|
||||
if tok(/=/) ||
|
||||
res = ['[', str{ss}, str{attrib_name!}, str{ss}]
|
||||
|
||||
if m = tok(/=/) ||
|
||||
tok(INCLUDES) ||
|
||||
tok(DASHMATCH) ||
|
||||
tok(PREFIXMATCH) ||
|
||||
tok(SUFFIXMATCH) ||
|
||||
tok(SUBSTRINGMATCH)
|
||||
ss
|
||||
@expected = "identifier or string"
|
||||
tok(IDENT) || tok!(STRING); ss
|
||||
res << m << str{ss} << (tok(IDENT) || expr!(:interp_string)) << str{ss}
|
||||
end
|
||||
tok!(/\]/)
|
||||
res << tok!(/\]/)
|
||||
end
|
||||
|
||||
def attrib_name!
|
||||
|
@ -341,34 +347,34 @@ module Sass
|
|||
end
|
||||
|
||||
def pseudo
|
||||
return unless tok(/::?/)
|
||||
return unless s = tok(/::?/)
|
||||
|
||||
@expected = "pseudoclass or pseudoelement"
|
||||
functional_pseudo || tok!(IDENT)
|
||||
[s, functional_pseudo || tok!(IDENT)]
|
||||
end
|
||||
|
||||
def functional_pseudo
|
||||
return unless tok FUNCTION
|
||||
ss
|
||||
expr! :pseudo_expr
|
||||
tok!(/\)/)
|
||||
return unless fn = tok(FUNCTION)
|
||||
[fn, str{ss}, expr!(:pseudo_expr), tok!(/\)/)]
|
||||
end
|
||||
|
||||
def pseudo_expr
|
||||
return unless tok(PLUS) || tok(/-/) || tok(NUMBER) ||
|
||||
tok(STRING) || tok(IDENT)
|
||||
ss
|
||||
ss while tok(PLUS) || tok(/-/) || tok(NUMBER) ||
|
||||
tok(STRING) || tok(IDENT)
|
||||
true
|
||||
return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
|
||||
interp_string || tok(IDENT) || interpolation
|
||||
res = [e, str{ss}]
|
||||
while e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
|
||||
interp_string || tok(IDENT) || interpolation
|
||||
res << e << str{ss}
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def negation
|
||||
return unless tok(NOT)
|
||||
ss
|
||||
res = [":not(", str{ss}]
|
||||
@expected = "selector"
|
||||
element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo)
|
||||
tok!(/\)/)
|
||||
res << (element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo))
|
||||
res << tok!(/\)/)
|
||||
end
|
||||
|
||||
def declaration
|
||||
|
@ -425,9 +431,10 @@ MESSAGE
|
|||
end
|
||||
|
||||
def expr
|
||||
return unless term
|
||||
nil while operator && term
|
||||
true
|
||||
[str do
|
||||
return unless term
|
||||
nil while operator && term
|
||||
end]
|
||||
end
|
||||
|
||||
def term
|
||||
|
@ -460,6 +467,25 @@ MESSAGE
|
|||
ss
|
||||
end
|
||||
|
||||
def interpolation
|
||||
return unless tok(/#\{/)
|
||||
sass_script(:parse_interpolated)
|
||||
end
|
||||
|
||||
def interp_string
|
||||
_interp_string(:double) || _interp_string(:single)
|
||||
end
|
||||
|
||||
def _interp_string(type)
|
||||
return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
|
||||
res = [start]
|
||||
|
||||
mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
|
||||
# @scanner[2].empty? means we've started an interpolated section
|
||||
res << expr!(:interpolation) << tok(mid_re) while @scanner[2].empty?
|
||||
res
|
||||
end
|
||||
|
||||
def str
|
||||
@strs.push ""
|
||||
yield
|
||||
|
|
|
@ -273,7 +273,7 @@ module Sass
|
|||
text.map do |r|
|
||||
next r if r.is_a?(String)
|
||||
r.perform(environment).to_s
|
||||
end.join
|
||||
end.join.strip
|
||||
end
|
||||
|
||||
# @see Haml::Shared.balance
|
||||
|
|
|
@ -50,8 +50,10 @@ module Sass::Tree
|
|||
# @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
|
||||
# `:old` if it uses `:a b`-style syntax
|
||||
def initialize(name, value, prop_syntax)
|
||||
@name = name
|
||||
@value = value
|
||||
@name = Haml::Util.strip_string_array(
|
||||
Haml::Util.merge_adjacent_strings(name))
|
||||
@value = Haml::Util.strip_string_array(
|
||||
Haml::Util.merge_adjacent_strings(value))
|
||||
@tabs = 0
|
||||
@prop_syntax = prop_syntax
|
||||
super()
|
||||
|
|
|
@ -77,7 +77,8 @@ module Sass::Tree
|
|||
# @param rule [Array<String, Sass::Script::Node>]
|
||||
# The CSS rule. See \{#rule}
|
||||
def initialize(rule)
|
||||
@rule = rule
|
||||
@rule = Haml::Util.strip_string_array(
|
||||
Haml::Util.merge_adjacent_strings(rule))
|
||||
@tabs = 0
|
||||
super()
|
||||
end
|
||||
|
|
|
@ -66,6 +66,15 @@ class UtilTest < Test::Unit::TestCase
|
|||
merge_adjacent_strings(["foo ", "bar ", "baz", :bang, "biz", " bop", 12]))
|
||||
end
|
||||
|
||||
def test_strip_string_array
|
||||
assert_equal(["foo ", " bar ", " baz"],
|
||||
strip_string_array([" foo ", " bar ", " baz "]))
|
||||
assert_equal([:foo, " bar ", " baz"],
|
||||
strip_string_array([:foo, " bar ", " baz "]))
|
||||
assert_equal(["foo ", " bar ", :baz],
|
||||
strip_string_array([" foo ", " bar ", :baz]))
|
||||
end
|
||||
|
||||
def test_silence_warnings
|
||||
old_stderr, $stderr = $stderr, StringIO.new
|
||||
warn "Out"
|
||||
|
|
|
@ -520,6 +520,65 @@ CSS
|
|||
SCSS
|
||||
end
|
||||
|
||||
## Interpolation
|
||||
|
||||
def test_basic_selector_interpolation
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo 3 baz {
|
||||
a: b; }
|
||||
CSS
|
||||
foo \#{1 + 2} baz {a: b}
|
||||
SCSS
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo.bar baz {
|
||||
a: b; }
|
||||
CSS
|
||||
foo\#{".bar"} baz {a: b}
|
||||
SCSS
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo.bar baz {
|
||||
a: b; }
|
||||
CSS
|
||||
\#{"foo"}.bar baz {a: b}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_selector_only_interpolation
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo bar {
|
||||
a: b; }
|
||||
CSS
|
||||
\#{"foo" + " bar"} {a: b}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_selector_interpolation_before_element_name
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo barbaz {
|
||||
a: b; }
|
||||
CSS
|
||||
\#{"foo" + " bar"}baz {a: b}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_selector_interpolation_in_string
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo[val="bar foo bar baz"] {
|
||||
a: b; }
|
||||
CSS
|
||||
foo[val="bar \#{"foo" + " bar"} baz"] {a: b}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_selector_interpolation_in_pseudoclass
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo:nth-child(5n) {
|
||||
a: b; }
|
||||
CSS
|
||||
foo:nth-child(\#{5 + "n"}) {a: b}
|
||||
SCSS
|
||||
end
|
||||
|
||||
## Errors
|
||||
|
||||
def test_mixin_defs_only_at_toplevel
|
||||
|
|
Loading…
Add table
Reference in a new issue