1
0
Fork 0
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:
Nathan Weizenbaum 2010-05-17 22:16:06 -07:00
parent e44bcb2b25
commit 0061473362
8 changed files with 207 additions and 59 deletions

View file

@ -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).

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"'

View file

@ -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"')

View file

@ -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 {