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)
|
## 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.
|
* Fix Sass configuration under Rails 3.
|
||||||
Thanks [Dan Cheail](http://github.com/codeape).
|
Thanks [Dan Cheail](http://github.com/codeape).
|
||||||
|
|
||||||
|
|
|
@ -614,6 +614,7 @@ and equality operators
|
||||||
are supported for all types.
|
are supported for all types.
|
||||||
|
|
||||||
##### Division and `/`
|
##### Division and `/`
|
||||||
|
{#division-and-slash}
|
||||||
|
|
||||||
CSS allows `/` to appear in property values
|
CSS allows `/` to appear in property values
|
||||||
as a way of separating numbers.
|
as a way of separating numbers.
|
||||||
|
@ -648,6 +649,22 @@ is compiled to:
|
||||||
height: 250px;
|
height: 250px;
|
||||||
margin-left: 9px; }
|
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
|
#### Color Operations
|
||||||
|
|
||||||
All arithmetic operations are supported for color values,
|
All arithmetic operations are supported for color values,
|
||||||
|
@ -820,6 +837,24 @@ is compiled to:
|
||||||
p.foo {
|
p.foo {
|
||||||
border-color: blue; }
|
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`
|
### Variable Defaults: `!default`
|
||||||
|
|
||||||
You can assign to variables if they aren't already assigned
|
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 after [Node] The SassScript after the interpolation
|
||||||
# @param wb [Boolean] Whether there was whitespace between `before` and `#{`
|
# @param wb [Boolean] Whether there was whitespace between `before` and `#{`
|
||||||
# @param wa [Boolean] Whether there was whitespace between `}` and `after`
|
# @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
|
@before = before
|
||||||
@mid = mid
|
@mid = mid
|
||||||
@after = after
|
@after = after
|
||||||
@whitespace_before = wb
|
@whitespace_before = wb
|
||||||
@whitespace_after = wa
|
@whitespace_after = wa
|
||||||
|
@originally_text = originally_text
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [String] A human-readable s-expression representation of the interpolation
|
# @return [String] A human-readable s-expression representation of the interpolation
|
||||||
|
@ -28,7 +33,9 @@ module Sass::Script
|
||||||
res = ""
|
res = ""
|
||||||
res << @before.to_sass(opts) if @before
|
res << @before.to_sass(opts) if @before
|
||||||
res << ' ' if @before && @whitespace_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 << ' ' if @after && @whitespace_after
|
||||||
res << @after.to_sass(opts) if @after
|
res << @after.to_sass(opts) if @after
|
||||||
res
|
res
|
||||||
|
|
|
@ -175,6 +175,11 @@ module Sass
|
||||||
@scanner.eos? && @tok.nil?
|
@scanner.eos? && @tok.nil?
|
||||||
end
|
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
|
# Raise an error to the effect that `name` was expected in the input stream
|
||||||
# and wasn't found.
|
# and wasn't found.
|
||||||
#
|
#
|
||||||
|
@ -327,10 +332,6 @@ MESSAGE
|
||||||
def current_position
|
def current_position
|
||||||
@offset + 1
|
@offset + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_interpolation?
|
|
||||||
@prev && @prev.type == :end_interpolation
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -150,8 +150,10 @@ module Sass
|
||||||
def production(name, sub, *ops)
|
def production(name, sub, *ops)
|
||||||
class_eval <<RUBY
|
class_eval <<RUBY
|
||||||
def #{name}
|
def #{name}
|
||||||
|
interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}) and return interp
|
||||||
return unless e = #{sub}
|
return unless e = #{sub}
|
||||||
while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
|
while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
|
||||||
|
interp = try_op_before_interp(tok, e) and return interp
|
||||||
line = @lexer.line
|
line = @lexer.line
|
||||||
e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
|
e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
|
||||||
e.line = line
|
e.line = line
|
||||||
|
@ -164,7 +166,8 @@ RUBY
|
||||||
def unary(op, sub)
|
def unary(op, sub)
|
||||||
class_eval <<RUBY
|
class_eval <<RUBY
|
||||||
def unary_#{op}
|
def unary_#{op}
|
||||||
return #{sub} unless try_tok(:#{op})
|
return #{sub} unless tok = try_tok(:#{op})
|
||||||
|
interp = try_op_before_interp(tok) and return interp
|
||||||
line = @lexer.line
|
line = @lexer.line
|
||||||
op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
|
op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
|
||||||
op.line = line
|
op.line = line
|
||||||
|
@ -182,8 +185,31 @@ RUBY
|
||||||
production :expr, :interpolation, :comma
|
production :expr, :interpolation, :comma
|
||||||
production :equals, :interpolation, :single_eq
|
production :equals, :interpolation, :single_eq
|
||||||
|
|
||||||
def interpolation
|
def try_op_before_interp(op, prev = nil)
|
||||||
e = concat
|
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)
|
while interp = try_tok(:begin_interpolation)
|
||||||
wb = @lexer.whitespace?(interp)
|
wb = @lexer.whitespace?(interp)
|
||||||
line = @lexer.line
|
line = @lexer.line
|
||||||
|
|
|
@ -158,6 +158,49 @@ RUBY
|
||||||
assert_renders "\#{$bar}"
|
assert_renders "\#{$bar}"
|
||||||
end
|
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
|
def test_string_interpolation
|
||||||
assert_renders '"foo#{$bar}baz"'
|
assert_renders '"foo#{$bar}baz"'
|
||||||
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")
|
assert_equal Sass::Script::String.new("foo/bar"), eval("foo/bar")
|
||||||
end
|
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 #{"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"')
|
||||||
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
|
SCSS
|
||||||
end
|
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
|
def test_basic_prop_name_interpolation
|
||||||
assert_equal <<CSS, render(<<SCSS)
|
assert_equal <<CSS, render(<<SCSS)
|
||||||
foo {
|
foo {
|
||||||
|
|
Loading…
Add table
Reference in a new issue