Improve callable and value documentation

This documentation now targets external users, since these are part of
the public API.
This commit is contained in:
Natalie Weizenbaum 2018-01-05 17:59:40 -08:00
parent 8988c3c7fa
commit b5a838c9c5
6 changed files with 184 additions and 22 deletions

View File

@ -8,6 +8,19 @@
* `var()` may now be passed in place of multiple arguments to `rgb()`, `rgba()`,
`hsl()` and `hsla()`.
### Dart API
* Add a `functions` parameter to `compile()`, `compleString()`,
`compileAsync()`, and `compileStringAsync()`. This allows users to define
custom functions in Dart that can be invoked from Sass stylesheets.
* Expose the `Callable` and `AsyncCallable` types, which represent functions
that can be invoked from Sass.
* Expose the `Value` type and its subclasses, as well as the top-level
`sassTrue`, `sassFalse`, and `sassNull` values, which represent Sass values
that may be passed into or returned from custom functions.
## 1.0.0-beta.4
* Support unquoted imports in the indented syntax.

View File

@ -12,14 +12,80 @@ export 'callable/built_in.dart';
export 'callable/plain_css.dart';
export 'callable/user_defined.dart';
/// An interface for objects, such as functions and mixins, that can be invoked
/// from Sass by passing in arguments.
/// An interface functions and mixins that can be invoked from Sass by passing
/// in arguments.
///
/// This extends [AsyncCallable] because all synchronous callables are also
/// usable in asynchronous contexts. [Callable]s are usable with both the
/// synchronous and asynchronous `compile()` functions, and as such should be
/// used in preference to [AsyncCallable]s if possible.
///
/// When writing custom functions, it's important to make them as user-friendly
/// and as close to the standards set by Sass's core functions as possible. Some
/// good guidelines to follow include:
///
/// * Use `Value.assert*` methods, like [Value.assertString], to cast untyped
/// `Value` objects to more specific types. For values from the argument list,
/// pass in the argument name as well. This ensures that the user gets good
/// error messages when they pass in the wrong type to your function.
///
/// * Individual classes may have more specific `assert*` methods, like
/// [SassNumber.assertInt], which should be used when possible.
///
/// * In Sass, every value counts as a list. Functions should avoid casting
/// values to the `SassList` type, and should use the [Value.asList] method
/// instead.
///
/// * When manipulating values like lists, strings, and numbers that have
/// metadata (comma versus sepace separated, bracketed versus unbracketed,
/// quoted versus unquoted, units), the output metadata should match the input
/// metadata. For lists, the [Value.changeList] method can be used to do this
/// automatically.
///
/// * When in doubt, lists should default to comma-separated, strings should
/// default to quoted, and number should default to unitless.
abstract class Callable extends AsyncCallable {
/// Creates a callable with the given [name] and [arguments] that runs
/// [callback] when called.
///
/// The argument declaration is parsed from [arguments], which uses the same
/// syntax as an argument list written in Sass (not including parentheses).
/// The [arguments] list may be empty to indicate that the function takes no
/// arguments. Arguments may also have default values. Throws a
/// [SassFormatException] if parsing fails.
///
/// Any exceptions thrown by [callback] are automatically converted to Sass
/// errors and associated with the function call.
///
/// For example:
///
/// ```dart
/// new Callable("str-split", r'$string, $divider: " "', (arguments) {
/// var string = arguments[0].assertString("string");
/// var divider = arguments[1].assertString("divider");
/// return new SassList(
/// string.value.split(divider.value).map((substring) =>
/// new SassString(substring, quotes: string.hasQuotes)),
/// ListSeparator.comma);
/// });
/// ```
///
/// Callables may also take variable length argument lists. These are declared
/// the same way as in Sass, and are passed as the final argument to the
/// callback. For example:
///
/// ```dart
/// new Callable("str-join", r'$strings...', (arguments) {
/// var args = arguments.first as SassArgumentList;
/// var strings = args.map((arg) => arg.assertString()).toList();
/// return new SassString(strings.map((string) => string.text).join(),
/// quotes: strings.any((string) => string.hasQuotes));
/// });
/// ```
///
/// Note that the argument list is always an instance of [SassArgumentList],
/// which provides access to keyword arguments using
/// [SassArgumentList.keywords].
factory Callable(String name, String arguments,
Value callback(List<Value> arguments)) = BuiltInCallable;
}

View File

@ -7,16 +7,25 @@ import 'dart:async';
import '../value.dart';
import 'async_built_in.dart';
/// An interface for objects, such as functions and mixins, that can be invoked
/// from Sass by passing in arguments.
/// An interface functions and mixins that can be invoked from Sass by passing
/// in arguments.
///
/// This class represents callables that *need* to do asynchronous work. It's
/// only compatible with the asynchonous `compile()` methods. If a callback can
/// work synchronously, it should be a [Callable] instead.
///
/// See [Callable] for more details.
abstract class AsyncCallable {
/// The callable's name.
String get name;
/// Creates a callable with the given [name] and [arguments] that runs
/// [callback] when called.
///
/// The argument declaration is parsed from [arguments], which should not
/// include parentheses. Throws a [SassFormatException] if parsing fails.
///
/// See [new Callable] for more details.
factory AsyncCallable(String name, String arguments,
FutureOr<Value> callback(List<Value> arguments)) = AsyncBuiltInCallable;
}

View File

@ -26,7 +26,10 @@ export 'value/string.dart';
/// A SassScript value.
///
/// Note that all SassScript values are unmodifiable.
/// All SassScript values are unmodifiable. New values can be constructed using
/// subclass constructors like [new SassString]. Untyped values can be cast to
/// particular types using `assert*()` functions like [assertString], which
/// throw user-friendly error messages if they fail.
abstract class Value {
/// Whether the value will be represented in CSS as the empty string.
bool get isBlank => false;
@ -70,6 +73,9 @@ abstract class Value {
const Value();
/// Calls the appropriate visit method on [visitor].
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
T accept<T>(ValueVisitor<T> visitor);
/// Throws a [SassScriptException] if [this] isn't a boolean.
@ -78,42 +84,42 @@ abstract class Value {
/// a literal boolean.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
SassBoolean assertBoolean([String name]) =>
throw _exception("$this is not a boolean.", name);
/// Throws a [SassScriptException] if [this] isn't a color.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
SassColor assertColor([String name]) =>
throw _exception("$this is not a color.", name);
/// Throws a [SassScriptException] if [this] isn't a function reference.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
SassFunction assertFunction([String name]) =>
throw _exception("$this is not a function reference.", name);
/// Throws a [SassScriptException] if [this] isn't a map.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
SassMap assertMap([String name]) =>
throw _exception("$this is not a map.", name);
/// Throws a [SassScriptException] if [this] isn't a number.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
SassNumber assertNumber([String name]) =>
throw _exception("$this is not a number.", name);
/// Throws a [SassScriptException] if [this] isn't a string.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
SassString assertString([String name]) =>
throw _exception("$this is not a string.", name);
@ -125,7 +131,9 @@ abstract class Value {
/// [ParentSelector]s. Otherwise, they're considered parse errors.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
///
/// **Note:** this function should not be called outside the `sass` package.
SelectorList assertSelector({String name, bool allowParent: false}) {
var string = _selectorString(name);
try {
@ -145,7 +153,10 @@ abstract class Value {
/// [ParentSelector]s. Otherwise, they're considered parse errors.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
SimpleSelector assertSimpleSelector({String name, bool allowParent: false}) {
var string = _selectorString(name);
try {
@ -165,7 +176,10 @@ abstract class Value {
/// [ParentSelector]s. Otherwise, they're considered parse errors.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
CompoundSelector assertCompoundSelector(
{String name, bool allowParent: false}) {
var string = _selectorString(name);
@ -239,34 +253,58 @@ abstract class Value {
}
/// The SassScript `=` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value singleEquals(Value other) =>
new SassString("${toCssString()}=${other.toCssString()}");
/// The SassScript `>` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
SassBoolean greaterThan(Value other) =>
throw new SassScriptException('Undefined operation "$this > $other".');
/// The SassScript `>=` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
SassBoolean greaterThanOrEquals(Value other) =>
throw new SassScriptException('Undefined operation "$this >= $other".');
/// The SassScript `<` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
SassBoolean lessThan(Value other) =>
throw new SassScriptException('Undefined operation "$this < $other".');
/// The SassScript `<=` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
SassBoolean lessThanOrEquals(Value other) =>
throw new SassScriptException('Undefined operation "$this <= $other".');
/// The SassScript `*` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value times(Value other) =>
throw new SassScriptException('Undefined operation "$this * $other".');
/// The SassScript `%` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value modulo(Value other) =>
throw new SassScriptException('Undefined operation "$this % $other".');
/// The SassScript `+` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value plus(Value other) {
if (other is SassString) {
return new SassString(toCssString() + other.text,
@ -277,28 +315,49 @@ abstract class Value {
}
/// The SassScript `-` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value minus(Value other) =>
new SassString("${toCssString()}-${other.toCssString()}");
/// The SassScript `/` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value dividedBy(Value other) =>
new SassString("${toCssString()}/${other.toCssString()}");
/// The SassScript unary `+` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value unaryPlus() => new SassString("+${toCssString()}");
/// The SassScript unary `-` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value unaryMinus() => new SassString("-${toCssString()}");
/// The SassScript unary `/` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value unaryDivide() => new SassString("/${toCssString()}");
/// The SassScript unary `not` operation.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value unaryNot() => sassFalse;
/// Returns a copy of [this] without [SassNumber.asSlash] set.
///
/// If this isn't a [SassNumber], returns it as-is.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
Value withoutSlash() => this;
/// Returns a valid CSS representation of [this].

View File

@ -9,10 +9,13 @@ import '../value.dart';
/// A SassScript argument list.
///
/// An argument list comes from a rest argument, and may contain a keyword map
/// as well as the positional arguments.
/// An argument list comes from a rest argument. It's distinct from a normal
/// [SassList] in that it may contain a keyword map as well as the positional
/// arguments.
class SassArgumentList extends SassList {
/// The keywords attached to this argument list.
/// The keyword arguments attached to this argument list.
///
/// The argument names don't include `$`.
Map<String, Value> get keywords {
_wereKeywordsAccessed = true;
return _keywords;
@ -24,6 +27,9 @@ class SassArgumentList extends SassList {
///
/// This is used to determine whether to throw an exception about passing
/// unexpected keywords.
///
/// **Note:** this function should not be called outside the `sass` package.
/// It's not guaranteed to be stable across versions.
bool get wereKeywordsAccessed => _wereKeywordsAccessed;
var _wereKeywordsAccessed = false;

View File

@ -153,6 +153,11 @@ class SassNumber extends Value {
static const precision = 10;
/// The value of this number.
///
/// Note that due to details of floating-point arithmetic, this may be a
/// [double] even if [this] represents an int from Sass's perspective. Use
/// [isInt] to determine whether this is an integer, [asInt] to get its
/// integer value, or [assertInt] to do both at once.
final num value;
/// This number's numerator units.
@ -165,6 +170,10 @@ class SassNumber extends Value {
final String asSlash;
/// Whether [this] has any units.
///
/// If a function expects a number to have no units, it should use
/// [assertNoUnits]. If it expects the number to have a particular unit, it
/// should use [assertUnit].
bool get hasUnits => numeratorUnits.isNotEmpty || denominatorUnits.isNotEmpty;
/// Whether [this] is an integer, according to [fuzzyEquals].
@ -224,7 +233,7 @@ class SassNumber extends Value {
///
/// Throws a [SassScriptException] if [value] isn't an integer. If this came
/// from a function argument, [name] is the argument name (without the `$`).
/// It's used for debugging.
/// It's used for error reporting.
int assertInt([String name]) {
var integer = fuzzyAsInt(value);
if (integer != null) return integer;
@ -239,7 +248,7 @@ class SassNumber extends Value {
///
/// Throws a [SassScriptException] if this isn't an integer or if it isn't a
/// valid index for [list]. If this came from a function argument, [name] is
/// the argument name (without the `$`). It's used for debugging.
/// the argument name (without the `$`). It's used for error reporting.
int assertIndexFor(List list, [String name]) {
var sassIndex = assertInt(name);
if (sassIndex == 0) throw _exception("List index may not be 0.");
@ -256,7 +265,7 @@ class SassNumber extends Value {
/// If [value] is [fuzzyEquals] to [min] or [max], it's clamped to the
/// appropriate value. Otherwise, this throws a [SassScriptException]. If this
/// came from a function argument, [name] is the argument name (without the
/// `$`). It's used for debugging.
/// `$`). It's used for error reporting.
num valueInRange(num min, num max, [String name]) {
var result = fuzzyCheckRange(value, min, max);
if (result != null) return result;
@ -274,7 +283,7 @@ class SassNumber extends Value {
/// (and as a numerator).
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
void assertUnit(String unit, [String name]) {
if (hasUnit(unit)) return;
throw _exception('Expected $this to have unit "$unit".', name);
@ -283,7 +292,7 @@ class SassNumber extends Value {
/// Throws a [SassScriptException] unless [this] has no units.
///
/// If this came from a function argument, [name] is the argument name
/// (without the `$`). It's used for debugging.
/// (without the `$`). It's used for error reporting.
void assertNoUnits([String name]) {
if (!hasUnits) return;
throw _exception('Expected $this to have no units.', name);