Data types in Sass
If we look at the base-font-sizes-calc()
function, we know it takes in a font family and then calculates the correct value to return. However, what if someone passed in a number? It's reasonable to assume something called base-font-sizes-calc()
deals with numbers, right? So we need to make sure we are only getting a string, and if not we need to tell the user this function needs a string which is the font-family and not a number.
Sass has 7 main data types (8 if you count arg
list):
string [unquoted, "quoted string", 'single quoted string']
number [1, 0.5, 1rem, 5px, 2em]
bool [true, false]
null [null]
list [(1, 2, 3, 4), ("one" "two" "three")]
map [(h1: 2rem, h2: 1.5rem, h3: 1.17rem)]
color [#bada55, red, hsl(10, 30%, 50%), rgb(255, 160, 80)]
The way you check for a specific type is with the Sass function type-of()
. So now we can update our function to perform this check with a simple ifelse
statement:
// mastering-sass/ch02/scss/helpers/_functions.scss @function base-font-size-calc($current-font-family: $base-font-family) { @if type-of($current-font-family) != string { @error "The base-font-size-calc() function takes a string as it's parameter, #{type-of($current-font-family)} type was given."; } @else { @return if($current-font-family == $base-font-family-serif, $base-font-size * 1.15, $base-font-size); } }
We can quickly make sure our error is showing by creating a simple CSS rule and entering the wrong data type into our function:
// mastering-sass/ch02/scss/style.scss .test-error { content: base-font-size-calc(1); }
This will output the following message to the command line:
The base-font-size-calc() function takes a string as it's parameter, number type was given.
You can enter other data types and it will simply replace number
in our message with the data type. This is due to the interpolated call to #{type-of($current-font-family)}
, which allows for a more dynamic and helpful error message.
Let's look at different data types in detail.
@error
In the preceding example we used @error
to product our message. Due to the fact functions a required to return a value, @error
is the only one of the three error directives @error
, @warn
, and @debug
which actually returns. However, this is the only reason I chose @error
here.
@error
is the highest priority level error message we have at our disposal. It stops everything once it's called and to the command line. This can't be turned off (@warn
can be turned off at runtime with the --quiet
option in Sass) and is ideal for any situation where an error cannot be overlooked or worked around. Adding a number instead of a font family is such an occurrence.
We'll mostly be dealing with @error
for now, however let's take a look at the differences between all three error directives right now. As you've seen, @error
stops Sass from compiling and displays the error in the command line. @warn
and @debug
do not stop Sass compiling, but they do display messages. I think of them as red, orange, and yellow level errors. @error
is red, @warn
is orange, and @debug
is yellow.
@debug
@debug
is the least severe. In fact, I wouldn't even call it an error directive. As the name implies it's mainly for debugging your functions and mixin during the process of development. It's should not be used in the finished production-ready Sass. It should be used much how we used the content property in our .test-error
rule discussed previously. So let's write a rule with @debug
to see the difference. We'll call our CSS the rule .test-debug
and output base- font-size-calc($base-headings-font-family)
:
// mastering-sass/ch02/scss/style.scss .test-debug { @debug base-font-size-calc($base-headings-font-family); }
In the command line you should see something like this:
>>> Change detected to: scss/style.scss /style.scss:40 DEBUG: 1.15rem write css/style.css write css/style.css.map
So it gives us the filename /style.scss
, the line on which @debug
was called, :40
, and the value: DEBUG: 1.15
. Sass will then continue to compile our css
files as normal. However, what happens if we put a number in base-font-size-calc()
like the previous example? Let's give it a go:
// mastering-sass/ch02/scss/style.scss .test-debug { @debug base-font-size-calc(1); }
Now in the command line you'll see the error with no debug info:
>>> Change detected to: scss/style.scss error scss/helpers/_functions.scss (Line 4: base-font-size-calc() takes a string as it's parameter. number given.)
This is because @error
will always win. If you call something that results in an error being output, the error will simply prevent everything else from happening and therefore will be the only thing output to the command line.
@warn
@warn
is useful if you want to show a message to a user in the command line without stopping Sass from compiling. However, @warn
is useful in final production-ready Sass. @warn
is useful when a function or mixin is being deprecated and therefore will still work for a time, but perhaps the user should be notified to use another function or mixin instead.
So let's assume one day another library comes out that also has a mixin called base-headings()
and we realize it's madness not to be name spacing our mixins and functions. So we are going to deprecate our base-headings()
mixin for a mixin called mastering-sass-base-headings()
. To do this we would use the @warn
directive to alert those using our mixins that in a future release they will have to start using the new mixin, but in the meantime their code will still work:
// mastering-sass/scss/helpers/_mixins.scss
@mixin base-headings {
@warn "Please use @include mastering-sass-base-headings() instead of @include base-headings(). @mixin base-headings() will be deprecated in version 2.";
$i: 1;
@each $heading, $family in $base-headings {
#{$heading} {
@include base-headings-font-family-sizing($i, $family);
}
$i: $i + 1;
}
}
Then we would obviously need to copy our base-headings
mixin and call it mastering-sass-base-headings
so it can be used as well in the transition period, and of course when we remove the availability of the base-headings mixin.
Now that we've looked at each of the error directives let's get back to adding our error checking to our functions and mixins. Our next function is base-heading-sizes-calc
. This function actually requires a few checks to be robust enough to be release into the wild web. We need to make sure the first parameter is a number and the second is a string. We then need to provide a user friendly error if a number greater than 6 is entered for our headings. First let's do our type checking and test that works:
// mastering-sass/ch02/scss/helpers/_functions.scss @function base-heading-sizes-calc($heading: 2, $font-family: $base-headings-font-family) { @if type-of($heading) != number { @error "The first parameter of base-heading-sizes-calc() must be a number, #{type-of($heading)} was given."; } @else if type-of($font-family) != string { @error "The second parameter base-heading-sizes-calc() must be a string, #{type-of($font-family)} was given."; } @else { $h4-font-size: base-font-size-calc($font-family); $h1-font-size: $h4-font-size * 2; $h2-font-size: $h1-font-size / 1.3333; $h3-font-size: $h2-font-size / 1.2821; $h5-font-size: $h4-font-size / 1.2048; $h6-font-size: $h5-font-size / 1.2388; $headings: $h1-font-size, $h2-font-size, $h3-font-size, $h4-font-size, $h5-font-size, $h6-font-size; @return nth($headings, $heading); } }
Now let's do a quick test to ensure it's working correctly:
// mastering-sass/ch02/scss/style.scss .test-data-types { content: base-heading-sizes-calc("", 2); }
This will give the following error in the command line:
error scss/helpers/_functions.scss (Line 11: The first parameter of base-heading-sizes-calc() must be a number, string was given.)
Again, this is because we can only get one error and then everything after that is prevented from running. So change the first parameter to a number and you should get the following error:
error scss/helpers/_functions.scss (Line 13: The second parameter of base-heading-sizes-calc() must be a string, number was given.)
Everything works, so we can move onto checking our number is from between 1 and 6 and not -1 or 0 or 7 or anything else which would could cause unexpected issues down the line. So once both variables have passed the data type, checks we'll end up inside the @else
block of our conditional. Therefore, we need to write our check inside our @else
statement, in a way that displays an error if the number given is outside of 1 and 6, otherwise our function runs the calculations as intended:
// mastering-sass/ch02/scss/helpers/_functions.scss @function base-heading-sizes-calc($heading: 2, $font-family: $base-headings-font-family) { @if type-of($heading) != number { @error "The first parameter of base-heading-sizes-calc() must be a number, #{type-of($heading)} was given."; } @else if type-of($font-family) != string { @error "The second parameter of base-heading-sizes-calc() must be a string, #{type-of($font-family)} was given."; } @else { @if $heading < 1 or $heading > 6 { @error "Second parameter of base-heading-sizes-calc() must be between 1 and 6, #{$heading} was given."; } $h4-font-size: base-font-size-calc($font-family); $h1-font-size: $h4-font-size * 2; $h2-font-size: $h1-font-size / 1.3333; $h3-font-size: $h2-font-size / 1.2821; $h5-font-size: $h4-font-size / 1.2048; $h6-font-size: $h5-font-size / 1.2388; $headings: $h1-font-size, $h2-font-size, $h3-font-size, $h4-font-size, $h5-font-size, $h6-font-size; @return nth($headings, $heading); } }
Now we test our error works correctly (you know what I mean):
.test-data-types { content: base-heading-sizes-calc(0, $base-headings-font-family); }
This should output the error message:
error sass/helpers/_functions.sass (Line 16: Second parameter of baseheading-sizes-calc() must be between 1 and 6, 0 was given.)
You'll notice we didn't wrap the rest of our code in an @else
block. This is because if the number is outside of 1 and 6 the error will trigger and stop the remainder of our function from running anyways, and if the number is between 1 and 6 the if
statement will simply be ignored and our calculations will run as usual. So for brevity we can leave the @else
out in these situations. The same would be true if we were returning a value. Once a function returns nothing else inside that function will run, similar to an error.
From here I think you how to check for errors. Basically, ask yourself the following:
- What happens if a user puts in the wrong data type?
- What happens if they enter a number which is outside of the intended range?
- Is this something I can account for without breaking the intending functionality?
Bonus round
I'm going to leave it up to you to go through the remaining functions and mixins and add useful error messages for any errors that may arise. However, for bonus points you could also add some smarts to the base-heading-sizes-calc
function. For example, I think we could safely assume if the value was less than 0 we could automatically set it to 1, and if it was greater than 6 we could set it to 6 and a warning should be displayed to the user instead of an error.