From 0072cce8888bbf0f1d3ddb462598e1b7f6ba8671 Mon Sep 17 00:00:00 2001 From: Pete Browne Date: Mon, 30 Apr 2012 07:44:42 -0500 Subject: [PATCH 1/4] Add null literal. --- doc-src/SASS_REFERENCE.md | 34 ++++++++++++++++++++- lib/sass/script/lexer.rb | 8 ++++- lib/sass/script/list.rb | 4 +-- lib/sass/script/literal.rb | 1 + lib/sass/script/null.rb | 34 +++++++++++++++++++++ lib/sass/script/operation.rb | 5 ++++ lib/sass/script/parser.rb | 2 +- test/sass/engine_test.rb | 37 +++++++++++++++++++++++ test/sass/functions_test.rb | 2 ++ test/sass/script_test.rb | 58 ++++++++++++++++++++++++++++++++++++ 10 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 lib/sass/script/null.rb diff --git a/doc-src/SASS_REFERENCE.md b/doc-src/SASS_REFERENCE.md index d766126106..f14ed3c5bf 100644 --- a/doc-src/SASS_REFERENCE.md +++ b/doc-src/SASS_REFERENCE.md @@ -627,12 +627,13 @@ this still works, but it's deprecated and prints a warning. ### Data Types -SassScript supports four main data types: +SassScript supports six main data types: * numbers (e.g. `1.2`, `13`, `10px`) * strings of text, with and without quotes (e.g. `"foo"`, `'bar'`, `baz`) * colors (e.g. `blue`, `#04a3f9`, `rgba(255, 0, 0, 0.5)`) * booleans (e.g. `true`, `false`) +* nulls, which are falsey (e.g. `null`) * lists of values, separated by spaces or commas (e.g. `1.5em 1em 0 2em`, `Helvetica, Arial, sans-serif`) SassScript also supports all other types of CSS property value, @@ -710,6 +711,8 @@ They can't be output directly to CSS; if you try to do e.g. `font-family: ()`, Sass will raise an error. If a list contains empty lists, as in `1px 2px () 3px`, the empty list will be removed before it's turned into CSS. +Null values are also be removed from lists, so `1px 2px null 3px` +would be become `1px 2px 3px` in CSS. ### Operations @@ -932,6 +935,20 @@ is compiled to: p:before { content: "I ate 15 pies!"; } +Variables with a null value are treated as an empty string in +string operations & interpolations: + + $value: null; + p:before { + content: "I ate #{$value} pies!"; + font-family: sans- + $value; } + +is compiled to: + + p:before { + content: "I ate pies!"; + font-family: sans-; } + #### Boolean Operations SassScript supports `and`, `or`, and `not` operators @@ -1046,6 +1063,21 @@ is compiled to: content: "First content"; new-content: "First time reference"; } +If variables are set to a `null` value, !default will treat +them like they're unassigned: + + $content: null; + $content: "Non-null content" !default; + + #main { + content: $content; + } + +is compiled to: + + #main { + content: "Non-null content"; } + ## `@`-Rules and Directives {#directives} Sass supports all CSS3 `@`-rules, diff --git a/lib/sass/script/lexer.rb b/lib/sass/script/lexer.rb index 60d86f9a04..bfbcfcf241 100644 --- a/lib/sass/script/lexer.rb +++ b/lib/sass/script/lexer.rb @@ -90,6 +90,7 @@ class Lexer :number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/, :color => HEXCOLOR, :bool => /(true|false)\b/, + :null => /null\b/, :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")})})}, :op => %r{(#{Regexp.union(*OP_NAMES)})}, } @@ -234,7 +235,7 @@ def token end variable || string(:double, false) || string(:single, false) || number || - color || bool || string(:uri, false) || raw(UNICODERANGE) || + color || bool || null || string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val || ident_op || ident || op end @@ -292,6 +293,11 @@ def bool [:bool, Script::Bool.new(s == 'true')] end + def null + return unless scan(REGULAR_EXPRESSIONS[:null]) + [:null, Script::Null.new] + end + def special_fun return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1) diff --git a/lib/sass/script/list.rb b/lib/sass/script/list.rb index 7eec1e432c..084b6124e3 100644 --- a/lib/sass/script/list.rb +++ b/lib/sass/script/list.rb @@ -41,14 +41,14 @@ def eq(other) # @see Node#to_s def to_s(opts = {}) raise Sass::SyntaxError.new("() isn't a valid CSS value.") if value.empty? - return value.reject {|e| e.is_a?(List) && e.value.empty?}.map {|e| e.to_s(opts)}.join(sep_str) + return value.reject {|e| e.is_a?(Null) || e.is_a?(List) && e.value.empty?}.map {|e| e.to_s(opts)}.join(sep_str) end # @see Node#to_sass def to_sass(opts = {}) return "()" if value.empty? precedence = Sass::Script::Parser.precedence_of(separator) - value.map do |v| + value.reject {|e| e.is_a?(Null)}.map do |v| if v.is_a?(List) && Sass::Script::Parser.precedence_of(v.separator) <= precedence "(#{v.to_sass(opts)})" else diff --git a/lib/sass/script/literal.rb b/lib/sass/script/literal.rb index 9bfcbef227..2c1fa2b31e 100644 --- a/lib/sass/script/literal.rb +++ b/lib/sass/script/literal.rb @@ -9,6 +9,7 @@ class Literal < Node require 'sass/script/number' require 'sass/script/color' require 'sass/script/bool' + require 'sass/script/null' require 'sass/script/list' # Returns the Ruby value of the literal. diff --git a/lib/sass/script/null.rb b/lib/sass/script/null.rb new file mode 100644 index 0000000000..acefe9ebfd --- /dev/null +++ b/lib/sass/script/null.rb @@ -0,0 +1,34 @@ +require 'sass/script/literal' + +module Sass::Script + # A SassScript object representing a null value. + class Null < Literal + # Creates a new null literal. + def initialize + super nil + end + + # @return [Boolean] `false` (the Ruby boolean value) + def to_bool + false + end + + # @return [Boolean] `true` + def nil? + true + end + + # @return [String] '' (An empty string) + def to_s(opts = {}) + '' + end + alias_method :to_sass, :to_s + + # Returns a string representing a null value. + # + # @return [String] + def inspect + 'null' + end + end +end \ No newline at end of file diff --git a/lib/sass/script/operation.rb b/lib/sass/script/operation.rb index 379a8bd7b1..3d57a1b208 100644 --- a/lib/sass/script/operation.rb +++ b/lib/sass/script/operation.rb @@ -82,6 +82,11 @@ def _perform(environment) literal2 = @operand2.perform(environment) + literal_types = [literal1.class, literal2.class] + if !literal_types.include?(String) && literal_types.include?(Null) + raise Sass::SyntaxError.new("Invalid null operation: \"#{literal1.inspect} #{@operator} #{literal2.inspect}\".") + end + begin opts(literal1.send(@operator, literal2)) rescue NoMethodError => e diff --git a/lib/sass/script/parser.rb b/lib/sass/script/parser.rb index 15830a491d..dcc630249b 100644 --- a/lib/sass/script/parser.rb +++ b/lib/sass/script/parser.rb @@ -453,7 +453,7 @@ def number end def literal - (t = try_tok(:color, :bool)) && (return t.value) + (t = try_tok(:color, :bool, :null)) && (return t.value) end # It would be possible to have unified #assert and #try methods, diff --git a/test/sass/engine_test.rb b/test/sass/engine_test.rb index c039b16c1b..74a59446ad 100755 --- a/test/sass/engine_test.rb +++ b/test/sass/engine_test.rb @@ -1100,6 +1100,7 @@ def test_property_with_content_and_nested_props def test_guarded_assign assert_equal("foo {\n a: b; }\n", render(%Q{$foo: b\n$foo: c !default\nfoo\n a: $foo})) assert_equal("foo {\n a: b; }\n", render(%Q{$foo: b !default\nfoo\n a: $foo})) + assert_equal("foo {\n a: b; }\n", render(%Q{$foo: null\n$foo: b !default\nfoo\n a: $foo})) end def test_mixins @@ -1185,6 +1186,35 @@ def test_default_values_for_mixin_arguments +foo(#fff, 2px) three +foo(#fff, 2px, 3px) +SASS + assert_equal(< :new) } + assert_raise_message(Sass::SyntaxError, 'Invalid property: "b:" (no value).') { + render("a\n b: (null, null)", :property_syntax => :new) } + end + def test_root_level_pseudo_class_with_new_properties assert_equal(< :new)) :focus { diff --git a/test/sass/functions_test.rb b/test/sass/functions_test.rb index dd776aa676..010472bb3f 100755 --- a/test/sass/functions_test.rb +++ b/test/sass/functions_test.rb @@ -871,6 +871,7 @@ def test_type_of assert_equal("bool", evaluate("type-of(true)")) assert_equal("color", evaluate("type-of(#fff)")) assert_equal("color", evaluate("type-of($value: #fff)")) + assert_equal("null", evaluate("type-of(null)")) end def test_unit @@ -1011,6 +1012,7 @@ def test_index def test_if assert_equal("1px", evaluate("if(true, 1px, 2px)")) assert_equal("2px", evaluate("if(false, 1px, 2px)")) + assert_equal("2px", evaluate("if(null, 1px, 2px)")) end def test_keyword_args_rgb diff --git a/test/sass/script_test.rb b/test/sass/script_test.rb index 22def48678..207045df54 100755 --- a/test/sass/script_test.rb +++ b/test/sass/script_test.rb @@ -263,6 +263,10 @@ def test_booleans assert_equal "false", resolve("false") end + def test_null + assert_equal "", resolve("null") + end + def test_boolean_ops assert_equal "true", resolve("true and true") assert_equal "true", resolve("false or true") @@ -281,6 +285,18 @@ def test_boolean_ops assert_equal "false", resolve("false and 1") assert_equal "2", resolve("2 or 3") assert_equal "3", resolve("2 and 3") + + assert_equal "true", resolve("null or true") + assert_equal "true", resolve("true or null") + assert_equal "", resolve("null or null") + assert_equal "", resolve("null and true") + assert_equal "", resolve("true and null") + assert_equal "", resolve("null and null") + + assert_equal "true", resolve("not null") + + assert_equal "1", resolve("null or 1") + assert_equal "", resolve("null and 1") end def test_arithmetic_ops @@ -304,6 +320,12 @@ def test_string_ops assert_equal "true-1", resolve('true - 1') assert_equal '"foo"/"bar"', resolve('"foo" / "bar"') assert_equal "true/1", resolve('true / 1') + assert_equal "foo", resolve('"foo" + null') + assert_equal "foo", resolve('null + "foo"') + assert_equal '"foo"-', resolve('"foo" - null') + assert_equal '-"foo"', resolve('null - "foo"') + assert_equal '"foo"/', resolve('"foo" / null') + assert_equal '/"foo"', resolve('null / "foo"') assert_equal '-"bar"', resolve("- 'bar'") assert_equal "-true", resolve('- true') @@ -326,6 +348,33 @@ def test_relational_ops assert_equal "false", resolve("3 <= 2") end + def test_null_ops + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "null plus 1".') {eval("null + 1")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "null minus 1".') {eval("null - 1")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "null times 1".') {eval("null * 1")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "null div 1".') {eval("null / 1")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "null mod 1".') {eval("null % 1")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "1 plus null".') {eval("1 + null")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "1 minus null".') {eval("1 - null")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "1 times null".') {eval("1 * null")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "1 div null".') {eval("1 / null")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "1 mod null".') {eval("1 % null")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "1 gt null".') {eval("1 > null")} + assert_raise_message(Sass::SyntaxError, + 'Invalid null operation: "null lt 1".') {eval("null < 1")} + end + def test_equals assert_equal("true", resolve('"foo" == $foo', {}, env("foo" => Sass::Script::String.new("foo")))) @@ -424,6 +473,15 @@ def test_empty_list assert_raise_message(Sass::SyntaxError, "() isn't a valid CSS value.") {resolve("nth(append((), ()), 1)")} end + def test_list_with_nulls + assert_equal "1, 2, 3", resolve("1, 2, null, 3") + assert_equal "1 2 3", resolve("1 2 null 3") + assert_equal "1, 2, 3", resolve("1, 2, 3, null") + assert_equal "1 2 3", resolve("1 2 3 null") + assert_equal "1, 2, 3", resolve("null, 1, 2, 3") + assert_equal "1 2 3", resolve("null 1 2 3") + end + def test_deep_argument_error_not_unwrapped assert_raise_message(ArgumentError, 'wrong number of arguments (0 for 1)') {resolve("arg-error()")} end From 7e0d57a1fa41d1c4e8fbd48bc551c7aeaafdcf2d Mon Sep 17 00:00:00 2001 From: Pete Browne Date: Thu, 10 May 2012 20:19:13 -0500 Subject: [PATCH 2/4] Omit properties with empty resolved values. --- lib/sass/tree/prop_node.rb | 2 +- lib/sass/tree/visitors/to_css.rb | 7 ++++--- test/sass/engine_test.rb | 21 +++++++++++++++------ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/sass/tree/prop_node.rb b/lib/sass/tree/prop_node.rb index 7818776302..82890885b6 100644 --- a/lib/sass/tree/prop_node.rb +++ b/lib/sass/tree/prop_node.rb @@ -98,7 +98,7 @@ def check! if @options[:property_syntax] && @options[:property_syntax] != @prop_syntax raise Sass::SyntaxError.new( "Illegal property syntax: can't use #{@prop_syntax} syntax when :property_syntax => #{@options[:property_syntax].inspect} is set.") - elsif resolved_value.empty? + elsif value.is_a?(Sass::Script::String) && value.to_s.empty? raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value)." + pseudo_class_selector_message) end diff --git a/lib/sass/tree/visitors/to_css.rb b/lib/sass/tree/visitors/to_css.rb index fcac5aab54..29dd7213f8 100644 --- a/lib/sass/tree/visitors/to_css.rb +++ b/lib/sass/tree/visitors/to_css.rb @@ -122,6 +122,7 @@ def visit_cssimport(node) end def visit_prop(node) + return if node.resolved_value =~ /\A[\s'"()]*\Z/ tab_str = ' ' * (@tabs + node.tabs) if node.style == :compressed "#{tab_str}#{node.resolved_name}:#{node.resolved_value}" @@ -188,13 +189,13 @@ def visit_rule(node) end if node.style == :compact - properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(' ')} + properties = with_tabs(0) {node.children.map {|a| visit(a)}.compact.join(' ')} to_return << "#{total_rule} { #{properties} }#{"\n" if node.group_end}" elsif node.style == :compressed - properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(';')} + properties = with_tabs(0) {node.children.map {|a| visit(a)}.compact.join(';')} to_return << "#{total_rule}{#{properties}}" else - properties = with_tabs(@tabs + 1) {node.children.map {|a| visit(a)}.join("\n")} + properties = with_tabs(@tabs + 1) {node.children.map {|a| visit(a)}.compact.join("\n")} end_props = (node.style == :expanded ? "\n" + old_spaces : ' ') to_return << "#{total_rule} {\n#{properties}#{end_props}}#{"\n" if node.group_end}" end diff --git a/test/sass/engine_test.rb b/test/sass/engine_test.rb index 74a59446ad..e854c8c89f 100755 --- a/test/sass/engine_test.rb +++ b/test/sass/engine_test.rb @@ -1011,7 +1011,7 @@ def test_debug_info def test_debug_info_without_filename assert_equal(< true).render) -@media -sass-debug-info{filename{font-family:}line{font-family:\\000031}} +@media -sass-debug-info{filename{}line{font-family:\\000031}} foo { a: b; } CSS @@ -1773,11 +1773,20 @@ def test_empty_selector_warning END end - def test_empty_property_error - assert_raise_message(Sass::SyntaxError, 'Invalid property: "b:" (no value).') { - render("a\n b: null", :property_syntax => :new) } - assert_raise_message(Sass::SyntaxError, 'Invalid property: "b:" (no value).') { - render("a\n b: (null, null)", :property_syntax => :new) } + def test_nonprinting_empty_property + assert_equal(< Date: Fri, 11 May 2012 14:43:41 -0700 Subject: [PATCH 3/4] Style changes. This has some test failures, since "foo {a:}" is currently not parsing as an error. --- doc-src/SASS_REFERENCE.md | 16 +++++++--------- lib/sass/script/literal.rb | 7 +++++++ lib/sass/script/null.rb | 4 ++-- lib/sass/tree/prop_node.rb | 10 +++++++--- lib/sass/tree/visitors/perform.rb | 3 ++- lib/sass/tree/visitors/to_css.rb | 8 ++++---- test/sass/engine_test.rb | 19 ++++++++++++++++--- 7 files changed, 45 insertions(+), 22 deletions(-) diff --git a/doc-src/SASS_REFERENCE.md b/doc-src/SASS_REFERENCE.md index 54d1e51c7f..c96d326c40 100644 --- a/doc-src/SASS_REFERENCE.md +++ b/doc-src/SASS_REFERENCE.md @@ -633,7 +633,7 @@ SassScript supports six main data types: * strings of text, with and without quotes (e.g. `"foo"`, `'bar'`, `baz`) * colors (e.g. `blue`, `#04a3f9`, `rgba(255, 0, 0, 0.5)`) * booleans (e.g. `true`, `false`) -* nulls, which are falsey (e.g. `null`) +* nulls (e.g. `null`) * lists of values, separated by spaces or commas (e.g. `1.5em 1em 0 2em`, `Helvetica, Arial, sans-serif`) SassScript also supports all other types of CSS property value, @@ -709,10 +709,10 @@ Lists can also have no items in them at all. These lists are represented as `()`. They can't be output directly to CSS; if you try to do e.g. `font-family: ()`, Sass will raise an error. -If a list contains empty lists, as in `1px 2px () 3px`, -the empty list will be removed before it's turned into CSS. -Null values are also be removed from lists, so `1px 2px null 3px` -would be become `1px 2px 3px` in CSS. +If a list contains empty lists or null values, +as in `1px 2px () 3px` or `1px 2px null 3px`, +the empty lists and null values will be removed +before the containing list is turned into CSS. ### Operations @@ -935,8 +935,7 @@ is compiled to: p:before { content: "I ate 15 pies!"; } -Variables with a null value are treated as an empty string in -string operations & interpolations: +Null values are treated as empty strings for string operations and interpolations: $value: null; p:before { @@ -1063,8 +1062,7 @@ is compiled to: content: "First content"; new-content: "First time reference"; } -If variables are set to a `null` value, !default will treat -them like they're unassigned: +Variables with `null` values are treated as unassigned by !default: $content: null; $content: "Non-null content" !default; diff --git a/lib/sass/script/literal.rb b/lib/sass/script/literal.rb index 2c1fa2b31e..683bdf95b4 100644 --- a/lib/sass/script/literal.rb +++ b/lib/sass/script/literal.rb @@ -218,6 +218,13 @@ def to_s(opts = {}) end alias_method :to_sass, :to_s + # Returns whether or not this object is null. + # + # @return [Boolean] `false` + def null? + false + end + protected # Evaluates the literal. diff --git a/lib/sass/script/null.rb b/lib/sass/script/null.rb index acefe9ebfd..4f90f59a88 100644 --- a/lib/sass/script/null.rb +++ b/lib/sass/script/null.rb @@ -14,7 +14,7 @@ def to_bool end # @return [Boolean] `true` - def nil? + def null? true end @@ -31,4 +31,4 @@ def inspect 'null' end end -end \ No newline at end of file +end diff --git a/lib/sass/tree/prop_node.rb b/lib/sass/tree/prop_node.rb index 82890885b6..43461d75ed 100644 --- a/lib/sass/tree/prop_node.rb +++ b/lib/sass/tree/prop_node.rb @@ -92,15 +92,19 @@ def declaration(opts = {:old => @prop_syntax == :old}, fmt = :sass) "#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip end + # A property node is invisible if its value is empty. + # + # @return [Boolean] + def invisible? + resolved_value.empty? + end + private def check! if @options[:property_syntax] && @options[:property_syntax] != @prop_syntax raise Sass::SyntaxError.new( "Illegal property syntax: can't use #{@prop_syntax} syntax when :property_syntax => #{@options[:property_syntax].inspect} is set.") - elsif value.is_a?(Sass::Script::String) && value.to_s.empty? - raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value)." + - pseudo_class_selector_message) end end diff --git a/lib/sass/tree/visitors/perform.rb b/lib/sass/tree/visitors/perform.rb index 23886cf243..cf8ba2eda3 100644 --- a/lib/sass/tree/visitors/perform.rb +++ b/lib/sass/tree/visitors/perform.rb @@ -261,7 +261,8 @@ def visit_rule(node) # Loads the new variable value into the environment. def visit_variable(node) - return [] if node.guarded && !@environment.var(node.name).nil? + var = @environment.var(node.name) + return [] if node.guarded && var && !var.null? val = node.expr.perform(@environment) @environment.set_var(node.name, val) [] diff --git a/lib/sass/tree/visitors/to_css.rb b/lib/sass/tree/visitors/to_css.rb index 29dd7213f8..cb90406032 100644 --- a/lib/sass/tree/visitors/to_css.rb +++ b/lib/sass/tree/visitors/to_css.rb @@ -122,7 +122,7 @@ def visit_cssimport(node) end def visit_prop(node) - return if node.resolved_value =~ /\A[\s'"()]*\Z/ + return if node.resolved_value.empty? tab_str = ' ' * (@tabs + node.tabs) if node.style == :compressed "#{tab_str}#{node.resolved_name}:#{node.resolved_value}" @@ -189,13 +189,13 @@ def visit_rule(node) end if node.style == :compact - properties = with_tabs(0) {node.children.map {|a| visit(a)}.compact.join(' ')} + properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(' ')} to_return << "#{total_rule} { #{properties} }#{"\n" if node.group_end}" elsif node.style == :compressed - properties = with_tabs(0) {node.children.map {|a| visit(a)}.compact.join(';')} + properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(';')} to_return << "#{total_rule}{#{properties}}" else - properties = with_tabs(@tabs + 1) {node.children.map {|a| visit(a)}.compact.join("\n")} + properties = with_tabs(@tabs + 1) {node.children.map {|a| visit(a)}.join("\n")} end_props = (node.style == :expanded ? "\n" + old_spaces : ' ') to_return << "#{total_rule} {\n#{properties}#{end_props}}#{"\n" if node.group_end}" end diff --git a/test/sass/engine_test.rb b/test/sass/engine_test.rb index 3695663c12..796c541c98 100755 --- a/test/sass/engine_test.rb +++ b/test/sass/engine_test.rb @@ -1206,9 +1206,9 @@ def test_default_values_for_mixin_arguments $a: 5px =foo($a, $b: 1px, $c: null) $c: 3px + $b !default - :color $a - :padding $b - :margin $c + color: $a + padding: $b + margin: $c one +foo(#fff) two @@ -1423,6 +1423,15 @@ def test_complex_property_interpolation def test_if_directive assert_equal("a {\n b: 1; }\n", render(< Date: Fri, 11 May 2012 15:02:07 -0700 Subject: [PATCH 4/4] Better errors for legitimately empty properties. --- lib/sass/engine.rb | 9 ++++++++- lib/sass/scss/rx.rb | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/sass/engine.rb b/lib/sass/engine.rb index 25e24b8b37..76f528fae7 100644 --- a/lib/sass/engine.rb +++ b/lib/sass/engine.rb @@ -600,7 +600,14 @@ def parse_property(name, parsed_name, value, prop, line) else expr = parse_script(value, :offset => line.offset + line.text.index(value)) end - Tree::PropNode.new(parse_interp(name), expr, prop) + node = Tree::PropNode.new(parse_interp(name), expr, prop) + if value.strip.empty? && line.children.empty? + raise SyntaxError.new( + "Invalid property: \"#{node.declaration}\" (no value)." + + node.pseudo_class_selector_message) + end + + node end def parse_variable(line) diff --git a/lib/sass/scss/rx.rb b/lib/sass/scss/rx.rb index 521bc0b967..08f76cab76 100644 --- a/lib/sass/scss/rx.rb +++ b/lib/sass/scss/rx.rb @@ -130,7 +130,7 @@ def self.quote(str, flags = 0) # about 50 characters. This mitigates the problem of exponential parsing # time when a value has a long string of valid, parsable content followed # by something invalid. - STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){0,50}([;}])/i + STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){1,50}([;}])/i STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){0,50}([{])/i end end