1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00

[Sass] [css2sass] Add a subclass of SCSS::Parser that only parses CSS.

This commit is contained in:
Nathan Weizenbaum 2010-01-06 22:42:13 -08:00
parent 00e0a25d7c
commit 658c44e2a5
4 changed files with 151 additions and 52 deletions

View file

@ -1,6 +1,6 @@
require File.dirname(__FILE__) + '/../sass'
require 'sass/tree/node'
require 'sass/scss/parser'
require 'sass/scss/css_parser'
require 'strscan'
module Sass
@ -100,7 +100,7 @@ module Sass
#
# @return [Tree::Node] The root node of the parsed tree
def build_tree
root = Sass::SCSS::Parser.new(@template).parse
root = Sass::SCSS::CssParser.new(@template).parse
expand_commas root
parent_ref_rules root
remove_parent_refs root

View file

@ -0,0 +1,30 @@
module Sass
module SCSS
class CssParser < Parser
private
def scss_directive(name); nil; end
def variable; nil; end
def parent_selector; nil; end
def script_value; nil; end
def interpolation; nil; end
def interp_string; tok(STRING); end
def expected_property_separator; '":"'; end
def block_child(context)
case context
when :ruleset
declaration
when :stylesheet
directive || ruleset
when :directive
directive || declaration_or_ruleset
end
end
def nested_properties!(node, expression, space)
expected('expression (e.g. 1px, bold)');
end
end
end
end

View file

@ -32,7 +32,7 @@ module Sass
def stylesheet
node = node(Sass::Tree::RootNode.new(@scanner.string))
block_contents(node) {s(node)}
block_contents(node, :stylesheet) {s(node)}
end
def s(node)
@ -71,8 +71,9 @@ module Sass
name = tok!(IDENT)
ss
sym = name.gsub('-', '_').to_sym
return send(sym) if DIRECTIVES.include?(sym)
if dir = scss_directive(name)
return dir
end
val = str do
# Most at-rules take expressions (e.g. @media, @import),
@ -82,18 +83,23 @@ module Sass
node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
if tok(/\{/)
block_contents(node)
block_contents(node, :directive)
tok!(/\}/)
end
node
end
def scss_directive(name)
sym = name.gsub('-', '_').to_sym
DIRECTIVES.include?(sym) && send(sym)
end
def mixin
name = tok! IDENT
args = sass_script(:parse_mixin_definition_arglist)
ss
block(node(Sass::Tree::MixinDefNode.new(name, args)))
block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
end
def include
@ -121,19 +127,19 @@ module Sass
to = sass_script(:parse)
ss
block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)))
block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
end
def while
expr = sass_script(:parse)
ss
block(node(Sass::Tree::WhileNode.new(expr)))
block(node(Sass::Tree::WhileNode.new(expr)), :directive)
end
def if
expr = sass_script(:parse)
ss
block(node(Sass::Tree::IfNode.new(expr)))
block(node(Sass::Tree::IfNode.new(expr)), :directive)
end
def import
@ -209,28 +215,28 @@ module Sass
rules.concat expr!(:selector)
end
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)))
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
end
def block(node)
def block(node, context)
tok!(/\{/)
block_contents(node)
block_contents(node, context)
tok!(/\}/)
node
end
# A block may contain declarations and/or rulesets
def block_contents(node)
def block_contents(node, context)
block_given? ? yield : ss_comments(node)
node << (child = block_child)
node << (child = block_child(context))
while tok(/;/) || (child && !child.children.empty?)
block_given? ? yield : ss_comments(node)
node << (child = block_child)
node << (child = block_child(context))
end
node
end
def block_child
def block_child(context)
variable || directive || declaration_or_ruleset
end
@ -294,7 +300,7 @@ module Sass
def simple_selector_sequence
# 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
attrib || negation || pseudo || parent_selector || interpolation
res = [e]
# The tok(/\*/) allows the "E*" hack
@ -305,6 +311,10 @@ module Sass
res
end
def parent_selector
tok(/&/)
end
def class_expr
return unless tok(/\./)
'.' + tok!(IDENT)
@ -391,30 +401,41 @@ module Sass
return unless name = property
end
value =
if tok(/=/)
require_block = false
expression = true
space = true
@use_property_exception = true
[sass_script(:parse)]
else
@expected = '":" or "="'
tok!(/:/)
space = !str {ss}.empty?
@use_property_exception ||= space || !tok?(IDENT)
expression = expr
expression << tok(IMPORTANT) if expression
require_block = !expression
expression || [""]
end
@expected = expected_property_separator
expression, space, value = (script_value || expr!(:plain_value))
ss
require_block ||= tok?(/\{/)
require_block = !expression || tok?(/\{/)
node = node(Sass::Tree::PropNode.new(name.flatten.compact, value.flatten.compact, :new))
if require_block && expression && !space
return node unless require_block
nested_properties! node, expression, space
end
def expected_property_separator
'":" or "="'
end
def script_value
return unless tok(/=/)
@use_property_exception = true
# expression, space, value
return true, true, [sass_script(:parse)]
end
def plain_value
return unless tok(/:/)
space = !str {ss}.empty?
@use_property_exception ||= space || !tok?(IDENT)
expression = expr
expression << tok(IMPORTANT) if expression
# expression, space, value
return expression, space, expression || [""]
end
def nested_properties!(node, expression, space)
if expression && !space
@use_property_exception = true
raise Sass::SyntaxError.new(<<MESSAGE, :line => @line)
Invalid CSS: a space is required between a property and its definition
@ -422,11 +443,9 @@ when it has other properties nested beneath it.
MESSAGE
end
return node unless require_block
@use_property_exception = true
@expected = 'expression (e.g. 1px, bold) or "{"'
block(node)
block(node, :property)
end
def expr

View file

@ -1,5 +1,6 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/test_helper'
require 'sass/scss/css_parser'
# These tests just test the parsing of CSS
# (both standard and any hacks we intend to support).
@ -641,26 +642,75 @@ SCSS
end
def test_no_properties_at_toplevel
render <<SCSS
foo {a: b}
a: b;
assert_not_parses('pseudoclass or pseudoelement', 'a:<err> b;')
end
def test_no_scss_directives
assert_parses('@import "foo.sass";')
assert_parses <<SCSS
@mixin foo {
a: b; }
SCSS
assert(false, "Expected error")
rescue Sass::SyntaxError => e
assert_equal "Properties aren't allowed at the root of a document.", e.message
assert_equal 2, e.sass_line
end
def test_no_variables
assert_not_parses("selector or at-rule", "<err>!var = 12;")
assert_not_parses('"}"', "foo { <err>!var = 12; }")
end
def test_no_parent_selectors
assert_not_parses('"{"', "foo <err>&.bar {a: b}")
end
def test_no_script_values
assert_not_parses('":"', "foo {a <err>= b}")
end
def test_no_selector_interpolation
assert_not_parses('"{"', 'foo <err>#{"bar"}.baz {a: b}')
end
def test_no_prop_name_interpolation
assert_not_parses('":"', 'foo {a<err>#{"bar"}baz: b}')
end
def test_no_prop_val_interpolation
assert_not_parses('"}"', 'foo {a: b <err>#{"bar"} c}')
end
def test_no_string_interpolation
assert_parses <<SCSS
foo {
a: "bang \#{1 + "bar"} bip"; }
SCSS
end
def test_no_nested_rules
assert_not_parses('":"', 'foo {bar <err>{a: b}}')
assert_not_parses('"}"', 'foo {<err>.bar {a: b}}')
end
def test_no_nested_properties
assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: <err>{a: b}}')
assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: bang <err>{a: b}}')
end
def test_no_nested_directives
assert_not_parses('"}"', 'foo {<err>@bar {a: b}}')
end
private
def assert_valid_string(ident)
assert_equal
end
def assert_selector_parses(selector)
assert_parses <<SCSS
#{selector} {
a: b; }
SCSS
end
def render(scss, options = {})
tree = Sass::SCSS::CssParser.new(scss).parse
tree.options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
tree.render
end
end