Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add null literal.

  • Loading branch information...
commit 0072cce8888bbf0f1d3ddb462598e1b7f6ba8671 1 parent 50209c8
@petebrowne authored
View
34 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,
View
8 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)
View
4 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
View
1  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.
View
34 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
View
5 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
View
2  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,
View
37 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
@@ -1186,6 +1187,35 @@ def test_default_values_for_mixin_arguments
three
+foo(#fff, 2px, 3px)
SASS
+ assert_equal(<<CSS, render(<<SASS))
+one {
+ color: white;
+ padding: 1px;
+ margin: 4px; }
+
+two {
+ color: white;
+ padding: 2px;
+ margin: 5px; }
+
+three {
+ color: white;
+ padding: 2px;
+ margin: 3px; }
+CSS
+$a: 5px
+=foo($a, $b: 1px, $c: null)
+ $c: 3px + $b !default
+ :color $a
+ :padding $b
+ :margin $c
+one
+ +foo(#fff)
+two
+ +foo(#fff, 2px)
+three
+ +foo(#fff, 2px, 3px)
+SASS
end
def test_hyphen_underscore_insensitive_mixins
@@ -1743,6 +1773,13 @@ 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) }
+ end
+
def test_root_level_pseudo_class_with_new_properties
assert_equal(<<CSS, render(<<SASS, :property_syntax => :new))
:focus {
View
2  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
View
58 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
Please sign in to comment.
Something went wrong with that request. Please try again.