mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
[Sass] Make interpolation-parsing much more robust.
Closes gh-172
This commit is contained in:
parent
e44bcb2b25
commit
0061473362
8 changed files with 207 additions and 59 deletions
|
@ -5,6 +5,34 @@
|
|||
|
||||
## 3.0.5 (Unreleased)
|
||||
|
||||
### `#{}` Interpolation in Properties
|
||||
|
||||
Previously, using `#{}` in some places in properties
|
||||
would cause a syntax error.
|
||||
Now it can be used just about anywhere.
|
||||
|
||||
Note that when `#{}` is used near operators like `/`,
|
||||
those operators are treated as plain CSS
|
||||
rather than math operators.
|
||||
For example:
|
||||
|
||||
p {
|
||||
$font-size: 12px;
|
||||
$line-height: 30px;
|
||||
font: #{$font-size}/#{$line-height};
|
||||
}
|
||||
|
||||
is compiled to:
|
||||
|
||||
p {
|
||||
font: 12px/30px;
|
||||
}
|
||||
|
||||
This is useful, since normally {file:SASS_REFERENCE.md#division-and-slash
|
||||
a slash with variables is treated as division}.
|
||||
|
||||
### Rails 3 Support
|
||||
|
||||
* Fix Sass configuration under Rails 3.
|
||||
Thanks [Dan Cheail](http://github.com/codeape).
|
||||
|
||||
|
|
|
@ -614,6 +614,7 @@ and equality operators
|
|||
are supported for all types.
|
||||
|
||||
##### Division and `/`
|
||||
{#division-and-slash}
|
||||
|
||||
CSS allows `/` to appear in property values
|
||||
as a way of separating numbers.
|
||||
|
@ -648,6 +649,22 @@ is compiled to:
|
|||
height: 250px;
|
||||
margin-left: 9px; }
|
||||
|
||||
If you want to use variables along with a plain CSS `/`,
|
||||
you can use `#{}` to insert them.
|
||||
For example:
|
||||
|
||||
p {
|
||||
$font-size: 12px;
|
||||
$line-height: 30px;
|
||||
font: #{$font-size}/#{$line-height};
|
||||
}
|
||||
|
||||
is compiled to:
|
||||
|
||||
p {
|
||||
font: 12px/30px;
|
||||
}
|
||||
|
||||
#### Color Operations
|
||||
|
||||
All arithmetic operations are supported for color values,
|
||||
|
@ -820,6 +837,24 @@ is compiled to:
|
|||
p.foo {
|
||||
border-color: blue; }
|
||||
|
||||
It's also possible to use `#{}` to put SassScript into property values.
|
||||
In most cases this isn't any better than using a variable,
|
||||
but using `#{}` does mean that any operations near it
|
||||
will be treated as plain CSS.
|
||||
For example:
|
||||
|
||||
p {
|
||||
$font-size: 12px;
|
||||
$line-height: 30px;
|
||||
font: #{$font-size}/#{$line-height};
|
||||
}
|
||||
|
||||
is compiled to:
|
||||
|
||||
p {
|
||||
font: 12px/30px;
|
||||
}
|
||||
|
||||
### Variable Defaults: `!default`
|
||||
|
||||
You can assign to variables if they aren't already assigned
|
||||
|
|
|
@ -10,12 +10,17 @@ module Sass::Script
|
|||
# @param after [Node] The SassScript after the interpolation
|
||||
# @param wb [Boolean] Whether there was whitespace between `before` and `#{`
|
||||
# @param wa [Boolean] Whether there was whitespace between `}` and `after`
|
||||
def initialize(before, mid, after, wb, wa)
|
||||
# @param originally_text [Boolean]
|
||||
# Whether the original format of the interpolation was plain text,
|
||||
# not an interpolation.
|
||||
# This is used when converting back to SassScript.
|
||||
def initialize(before, mid, after, wb, wa, originally_text = false)
|
||||
@before = before
|
||||
@mid = mid
|
||||
@after = after
|
||||
@whitespace_before = wb
|
||||
@whitespace_after = wa
|
||||
@originally_text = originally_text
|
||||
end
|
||||
|
||||
# @return [String] A human-readable s-expression representation of the interpolation
|
||||
|
@ -28,7 +33,9 @@ module Sass::Script
|
|||
res = ""
|
||||
res << @before.to_sass(opts) if @before
|
||||
res << ' ' if @before && @whitespace_before
|
||||
res << '#{' << @mid.to_sass(opts) << '}'
|
||||
res << '#{' unless @originally_text
|
||||
res << @mid.to_sass(opts)
|
||||
res << '}' unless @originally_text
|
||||
res << ' ' if @after && @whitespace_after
|
||||
res << @after.to_sass(opts) if @after
|
||||
res
|
||||
|
|
|
@ -175,6 +175,11 @@ module Sass
|
|||
@scanner.eos? && @tok.nil?
|
||||
end
|
||||
|
||||
# @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
|
||||
def after_interpolation?
|
||||
@prev && @prev.type == :end_interpolation
|
||||
end
|
||||
|
||||
# Raise an error to the effect that `name` was expected in the input stream
|
||||
# and wasn't found.
|
||||
#
|
||||
|
@ -327,10 +332,6 @@ MESSAGE
|
|||
def current_position
|
||||
@offset + 1
|
||||
end
|
||||
|
||||
def after_interpolation?
|
||||
@prev && @prev.type == :end_interpolation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -150,8 +150,10 @@ module Sass
|
|||
def production(name, sub, *ops)
|
||||
class_eval <<RUBY
|
||||
def #{name}
|
||||
interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}) and return interp
|
||||
return unless e = #{sub}
|
||||
while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
|
||||
interp = try_op_before_interp(tok, e) and return interp
|
||||
line = @lexer.line
|
||||
e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
|
||||
e.line = line
|
||||
|
@ -164,8 +166,9 @@ RUBY
|
|||
def unary(op, sub)
|
||||
class_eval <<RUBY
|
||||
def unary_#{op}
|
||||
return #{sub} unless try_tok(:#{op})
|
||||
line = @lexer.line
|
||||
return #{sub} unless tok = try_tok(:#{op})
|
||||
interp = try_op_before_interp(tok) and return interp
|
||||
line = @lexer.line
|
||||
op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
|
||||
op.line = line
|
||||
op
|
||||
|
@ -182,8 +185,31 @@ RUBY
|
|||
production :expr, :interpolation, :comma
|
||||
production :equals, :interpolation, :single_eq
|
||||
|
||||
def interpolation
|
||||
e = concat
|
||||
def try_op_before_interp(op, prev = nil)
|
||||
return unless @lexer.peek.type == :begin_interpolation
|
||||
wb = @lexer.whitespace?(op)
|
||||
str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
|
||||
str.line = @lexer.line
|
||||
interp = Script::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text)
|
||||
interp.line = @lexer.line
|
||||
interpolation(interp)
|
||||
end
|
||||
|
||||
def try_ops_after_interp(ops, name)
|
||||
return unless @lexer.after_interpolation?
|
||||
return unless op = try_tok(*ops)
|
||||
interp = try_op_before_interp(op) and return interp
|
||||
|
||||
wa = @lexer.whitespace?
|
||||
str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
|
||||
str.line = @lexer.line
|
||||
interp = Script::Interpolation.new(nil, str, assert_expr(name), !:wb, wa, :originally_text)
|
||||
interp.line = @lexer.line
|
||||
return interp
|
||||
end
|
||||
|
||||
def interpolation(first = concat)
|
||||
e = first
|
||||
while interp = try_tok(:begin_interpolation)
|
||||
wb = @lexer.whitespace?(interp)
|
||||
line = @lexer.line
|
||||
|
|
|
@ -158,6 +158,49 @@ RUBY
|
|||
assert_renders "\#{$bar}"
|
||||
end
|
||||
|
||||
def test_interpolation_in_function
|
||||
assert_renders 'flabnabbit(#{1 + "foo"})'
|
||||
assert_renders 'flabnabbit($foo #{1 + "foo"}$baz)'
|
||||
assert_renders 'flabnabbit($foo #{1 + "foo"}#{2 + "bar"} $baz)'
|
||||
end
|
||||
|
||||
def test_interpolation_near_operators
|
||||
assert_renders '#{1 + 2} , #{3 + 4}'
|
||||
assert_renders '#{1 + 2}, #{3 + 4}'
|
||||
assert_renders '#{1 + 2} ,#{3 + 4}'
|
||||
assert_renders '#{1 + 2},#{3 + 4}'
|
||||
|
||||
assert_renders '3 / #{3 + 4}'
|
||||
assert_renders '3 /#{3 + 4}'
|
||||
assert_renders '3/ #{3 + 4}'
|
||||
assert_renders '3/#{3 + 4}'
|
||||
|
||||
assert_renders '#{1 + 2} * 7'
|
||||
assert_renders '#{1 + 2}* 7'
|
||||
assert_renders '#{1 + 2} *7'
|
||||
assert_renders '#{1 + 2}*7'
|
||||
|
||||
assert_renders '-#{1 + 2}'
|
||||
assert_renders '- #{1 + 2}'
|
||||
|
||||
assert_renders '5 + #{1 + 2} * #{3 + 4}'
|
||||
assert_renders '5 +#{1 + 2} * #{3 + 4}'
|
||||
assert_renders '5+#{1 + 2} * #{3 + 4}'
|
||||
assert_renders '#{1 + 2} * #{3 + 4} + 5'
|
||||
assert_renders '#{1 + 2} * #{3 + 4}+ 5'
|
||||
assert_renders '#{1 + 2} * #{3 + 4}+5'
|
||||
|
||||
assert_equal '5 / #{1 + 2} + #{3 + 4}', render('5 / (#{1 + 2} + #{3 + 4})')
|
||||
assert_equal '5 / #{1 + 2} + #{3 + 4}', render('5 /(#{1 + 2} + #{3 + 4})')
|
||||
assert_equal '5 / #{1 + 2} + #{3 + 4}', render('5 /( #{1 + 2} + #{3 + 4} )')
|
||||
assert_equal '#{1 + 2} + #{3 + 4} / 5', render('(#{1 + 2} + #{3 + 4}) / 5')
|
||||
assert_equal '#{1 + 2} + #{3 + 4} / 5', render('(#{1 + 2} + #{3 + 4})/ 5')
|
||||
assert_equal '#{1 + 2} + #{3 + 4} / 5', render('( #{1 + 2} + #{3 + 4} )/ 5')
|
||||
|
||||
assert_renders '#{1 + 2} + 2 + 3'
|
||||
assert_renders '#{1 + 2} +2 + 3'
|
||||
end
|
||||
|
||||
def test_string_interpolation
|
||||
assert_renders '"foo#{$bar}baz"'
|
||||
assert_renders '"foo #{$bar}baz"'
|
||||
|
|
|
@ -97,7 +97,63 @@ class SassScriptTest < Test::Unit::TestCase
|
|||
assert_equal Sass::Script::String.new("foo/bar"), eval("foo/bar")
|
||||
end
|
||||
|
||||
def test_interpolation
|
||||
def test_basic_interpolation
|
||||
assert_equal "foo3bar", resolve("foo\#{1 + 2}bar")
|
||||
assert_equal "foo3 bar", resolve("foo\#{1 + 2} bar")
|
||||
assert_equal "foo 3bar", resolve("foo \#{1 + 2}bar")
|
||||
assert_equal "foo 3 bar", resolve("foo \#{1 + 2} bar")
|
||||
assert_equal "foo 35 bar", resolve("foo \#{1 + 2}\#{2 + 3} bar")
|
||||
assert_equal "foo 3 5 bar", resolve("foo \#{1 + 2} \#{2 + 3} bar")
|
||||
assert_equal "3bar", resolve("\#{1 + 2}bar")
|
||||
assert_equal "foo3", resolve("foo\#{1 + 2}")
|
||||
assert_equal "3", resolve("\#{1 + 2}")
|
||||
end
|
||||
|
||||
def test_interpolation_in_function
|
||||
assert_equal 'flabnabbit(1foo)', resolve('flabnabbit(#{1 + "foo"})')
|
||||
assert_equal 'flabnabbit(foo 1foobaz)', resolve('flabnabbit(foo #{1 + "foo"}baz)')
|
||||
assert_equal('flabnabbit(foo 1foo2bar baz)',
|
||||
resolve('flabnabbit(foo #{1 + "foo"}#{2 + "bar"} baz)'))
|
||||
end
|
||||
|
||||
def test_interpolation_near_operators
|
||||
assert_equal '3 , 7', resolve('#{1 + 2} , #{3 + 4}')
|
||||
assert_equal '3, 7', resolve('#{1 + 2}, #{3 + 4}')
|
||||
assert_equal '3 ,7', resolve('#{1 + 2} ,#{3 + 4}')
|
||||
assert_equal '3,7', resolve('#{1 + 2},#{3 + 4}')
|
||||
|
||||
assert_equal '3 / 7', resolve('3 / #{3 + 4}')
|
||||
assert_equal '3 /7', resolve('3 /#{3 + 4}')
|
||||
assert_equal '3/ 7', resolve('3/ #{3 + 4}')
|
||||
assert_equal '3/7', resolve('3/#{3 + 4}')
|
||||
|
||||
assert_equal '3 * 7', resolve('#{1 + 2} * 7')
|
||||
assert_equal '3* 7', resolve('#{1 + 2}* 7')
|
||||
assert_equal '3 *7', resolve('#{1 + 2} *7')
|
||||
assert_equal '3*7', resolve('#{1 + 2}*7')
|
||||
|
||||
assert_equal '-3', resolve('-#{1 + 2}')
|
||||
assert_equal '- 3', resolve('- #{1 + 2}')
|
||||
|
||||
assert_equal '5 + 3 * 7', resolve('5 + #{1 + 2} * #{3 + 4}')
|
||||
assert_equal '5 +3 * 7', resolve('5 +#{1 + 2} * #{3 + 4}')
|
||||
assert_equal '5+3 * 7', resolve('5+#{1 + 2} * #{3 + 4}')
|
||||
assert_equal '3 * 7 + 5', resolve('#{1 + 2} * #{3 + 4} + 5')
|
||||
assert_equal '3 * 7+ 5', resolve('#{1 + 2} * #{3 + 4}+ 5')
|
||||
assert_equal '3 * 7+5', resolve('#{1 + 2} * #{3 + 4}+5')
|
||||
|
||||
assert_equal '5/3 + 7', resolve('5 / (#{1 + 2} + #{3 + 4})')
|
||||
assert_equal '5/3 + 7', resolve('5 /(#{1 + 2} + #{3 + 4})')
|
||||
assert_equal '5/3 + 7', resolve('5 /( #{1 + 2} + #{3 + 4} )')
|
||||
assert_equal '3 + 7/5', resolve('(#{1 + 2} + #{3 + 4}) / 5')
|
||||
assert_equal '3 + 7/5', resolve('(#{1 + 2} + #{3 + 4})/ 5')
|
||||
assert_equal '3 + 7/5', resolve('( #{1 + 2} + #{3 + 4} )/ 5')
|
||||
|
||||
assert_equal '3 + 5', resolve('#{1 + 2} + 2 + 3')
|
||||
assert_equal '3 +5', resolve('#{1 + 2} +2 + 3')
|
||||
end
|
||||
|
||||
def test_string_interpolation
|
||||
assert_equal "foo bar, baz bang", resolve('"foo #{"bar"}, #{"baz"} bang"')
|
||||
assert_equal "foo bar baz bang", resolve('"foo #{"#{"ba" + "r"} baz"} bang"')
|
||||
assert_equal 'foo #{bar baz} bang', resolve('"foo \#{#{"ba" + "r"} baz} bang"')
|
||||
|
|
|
@ -727,54 +727,6 @@ div { -foo-\#{$a}-\#{$b}-foo: foo }
|
|||
SCSS
|
||||
end
|
||||
|
||||
def test_basic_prop_val_interpolation
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo {
|
||||
bar: foo 3 baz; }
|
||||
CSS
|
||||
foo {bar: foo \#{1 + 2} baz}
|
||||
SCSS
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo {
|
||||
bar: foo3 baz; }
|
||||
CSS
|
||||
foo {bar: foo\#{1 + 2} baz}
|
||||
SCSS
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo {
|
||||
bar: foo 3, baz; }
|
||||
CSS
|
||||
foo {bar: foo \#{1 + 2},baz}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_prop_val_only_interpolation
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo {
|
||||
bar: bazbang; }
|
||||
CSS
|
||||
foo {bar: \#{"baz" + "bang"}}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_prop_val_interpolation_in_string
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo {
|
||||
bar: "bizzle bazbang bop"; }
|
||||
CSS
|
||||
foo {bar: "bizzle \#{"baz" + "bang"} bop"}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_prop_val_interpolation_in_function
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo {
|
||||
bar: flabnabbit(1foo); }
|
||||
CSS
|
||||
foo {bar: flabnabbit(\#{1 + "foo"})}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_basic_prop_name_interpolation
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
foo {
|
||||
|
|
Loading…
Add table
Reference in a new issue