SassNumber.assertIndexFor() -> Value.sassIndexToListIndex() (#211)

This commit is contained in:
Natalie Weizenbaum 2018-01-14 13:38:43 -08:00 committed by GitHub
parent 1e09cec5aa
commit 9d207b13ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 135 additions and 29 deletions

View File

@ -696,17 +696,17 @@ final List<BuiltInCallable> coreFunctions = new UnmodifiableListView([
(arguments) => new SassNumber(arguments[0].asList.length)),
new BuiltInCallable("nth", r"$list, $n", (arguments) {
var list = arguments[0].asList;
var index = arguments[1].assertNumber("n");
return list[index.assertIndexFor(list, "n")];
var list = arguments[0];
var index = arguments[1];
return list.asList[list.sassIndexToListIndex(index, "n")];
}),
new BuiltInCallable("set-nth", r"$list, $n, $value", (arguments) {
var list = arguments[0].asList;
var index = arguments[1].assertNumber("n");
var list = arguments[0];
var index = arguments[1];
var value = arguments[2];
var newList = list.toList();
newList[index.assertIndexFor(list, "n")] = value;
var newList = list.asList.toList();
newList[list.sassIndexToListIndex(index, "n")] = value;
return arguments[0].changeListContents(newList);
}),

View File

@ -2,6 +2,8 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:meta/meta.dart';
import 'ast/selector.dart';
import 'exception.dart';
import 'value/boolean.dart';
@ -36,6 +38,13 @@ abstract class Value implements ext.Value {
bool get hasBrackets => false;
List<Value> get asList => [this];
/// The length of [asList].
///
/// This is used to compute [sassIndexToListIndex] without allocating a new
/// list.
@protected
int get lengthAsList => 1;
/// Whether the value will be represented in CSS as the empty string.
bool get isBlank => false;
@ -61,6 +70,18 @@ abstract class Value implements ext.Value {
/// It's not guaranteed to be stable across versions.
T accept<T>(ValueVisitor<T> visitor);
int sassIndexToListIndex(ext.Value sassIndex, [String name]) {
var index = sassIndex.assertNumber(name).assertInt(name);
if (index == 0) throw _exception("List index may not be 0.", name);
if (index.abs() > lengthAsList) {
throw _exception(
"Invalid index $sassIndex for a list with ${lengthAsList} elements.",
name);
}
return index < 0 ? lengthAsList + index : index - 1;
}
SassBoolean assertBoolean([String name]) =>
throw _exception("$this is not a boolean.", name);

View File

@ -69,17 +69,6 @@ abstract class SassNumber extends Value {
/// It's used for error reporting.
int assertInt([String name]);
/// Asserts that this is a valid Sass-style index for [list], and returns the
/// Dart-style index.
///
/// A Sass-style index is one-based, and uses negative numbers to count
/// backwards from the end of the list.
///
/// 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 error reporting.
int assertIndexFor(List list, [String name]);
/// If [value] is between [min] and [max], returns it.
///
/// If [value] is [fuzzyEquals] to [min] or [max], it's clamped to the

View File

@ -58,6 +58,18 @@ abstract class Value {
/// and all other values count as single-value lists.
List<Value> get asList;
/// Converts [sassIndex] into a Dart-style index into the list returned by
/// [asList].
///
/// Sass indexes are one-based, while Dart indexes are zero-based. Sass
/// indexes may also be negative in order to index from the end of the list.
///
/// Throws a [SassScriptException] if [sassIndex] isn't a number, if that
/// number isn't an integer, or if that integer isn't a valid index for
/// [asList]. If [sassIndex] came from a function argument, [name] is the
/// argument name (without the `$`). It's used for error reporting.
int sassIndexToListIndex(Value sassIndex, [String name]);
/// Throws a [SassScriptException] if [this] isn't a boolean.
///
/// Note that generally, functions should use [isTruthy] rather than requiring

View File

@ -18,6 +18,8 @@ class SassList extends Value implements ext.SassList {
List<Value> get asList => contents;
int get lengthAsList => contents.length;
const SassList.empty({ListSeparator separator, bool brackets: false})
: contents = const [],
separator = separator ?? ListSeparator.undecided,

View File

@ -21,6 +21,8 @@ class SassMap extends Value implements ext.SassMap {
return result;
}
int get lengthAsList => contents.length;
/// Returns an empty map.
const SassMap.empty() : contents = const {};

View File

@ -200,17 +200,6 @@ class SassNumber extends Value implements ext.SassNumber {
throw _exception("$this is not an int.", name);
}
int assertIndexFor(List list, [String name]) {
var sassIndex = assertInt(name);
if (sassIndex == 0) throw _exception("List index may not be 0.");
if (sassIndex.abs() > list.length) {
throw _exception(
"Invalid index $this for a list with ${list.length} elements.");
}
return sassIndex < 0 ? list.length + sassIndex : sassIndex - 1;
}
num valueInRange(num min, num max, [String name]) {
var result = fuzzyCheckRange(value, min, max);
if (result != null) return result;

View File

@ -62,6 +62,39 @@ main() {
ListSeparator.comma))));
});
group("sassIndexToListIndex()", () {
test("converts a positive index to a Dart index", () {
expect(value.sassIndexToListIndex(new SassNumber(1)), equals(0));
expect(value.sassIndexToListIndex(new SassNumber(2)), equals(1));
expect(value.sassIndexToListIndex(new SassNumber(3)), equals(2));
});
test("converts a negative index to a Dart index", () {
expect(value.sassIndexToListIndex(new SassNumber(-1)), equals(2));
expect(value.sassIndexToListIndex(new SassNumber(-2)), equals(1));
expect(value.sassIndexToListIndex(new SassNumber(-3)), equals(0));
});
test("rejects a non-number", () {
expect(() => value.sassIndexToListIndex(new SassString("foo")),
throwsSassScriptException);
});
test("rejects a non-integer", () {
expect(() => value.sassIndexToListIndex(new SassNumber(1.1)),
throwsSassScriptException);
});
test("rejects invalid indices", () {
expect(() => value.sassIndexToListIndex(new SassNumber(0)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(4)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(-4)),
throwsSassScriptException);
});
});
test("isn't any other type", () {
expect(value.assertBoolean, throwsSassScriptException);
expect(value.assertColor, throwsSassScriptException);
@ -133,6 +166,15 @@ main() {
expect(value.assertNumber, throwsSassScriptException);
expect(value.assertString, throwsSassScriptException);
});
test("sassIndexToListIndex() rejects invalid indices", () {
expect(() => value.sassIndexToListIndex(new SassNumber(0)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(1)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(-1)),
throwsSassScriptException);
});
});
group("a scalar value", () {
@ -152,6 +194,25 @@ main() {
expect(list, hasLength(1));
expect(list.first, same(value));
});
group("sassIndexToListIndex()", () {
test("converts a positive index to a Dart index", () {
expect(value.sassIndexToListIndex(new SassNumber(1)), equals(0));
});
test("converts a negative index to a Dart index", () {
expect(value.sassIndexToListIndex(new SassNumber(-1)), equals(0));
});
test("rejects invalid indices", () {
expect(() => value.sassIndexToListIndex(new SassNumber(0)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(2)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(-2)),
throwsSassScriptException);
});
});
});
group("new SassList.empty()", () {

View File

@ -39,6 +39,27 @@ main() {
]));
});
group("sassIndexToListIndex()", () {
test("converts a positive index to a Dart index", () {
expect(value.sassIndexToListIndex(new SassNumber(1)), equals(0));
expect(value.sassIndexToListIndex(new SassNumber(2)), equals(1));
});
test("converts a negative index to a Dart index", () {
expect(value.sassIndexToListIndex(new SassNumber(-1)), equals(1));
expect(value.sassIndexToListIndex(new SassNumber(-2)), equals(0));
});
test("rejects invalid indices", () {
expect(() => value.sassIndexToListIndex(new SassNumber(0)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(3)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(-3)),
throwsSassScriptException);
});
});
test("equals the same map", () {
expect(
value,
@ -128,6 +149,15 @@ main() {
test("equals an empty list", () {
expect(value, equalsWithHash(new SassList.empty()));
});
test("sassIndexToListIndex() rejects invalid indices", () {
expect(() => value.sassIndexToListIndex(new SassNumber(0)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(1)),
throwsSassScriptException);
expect(() => value.sassIndexToListIndex(new SassNumber(-1)),
throwsSassScriptException);
});
});
test("new SassMap.empty() creates an empty map with default metadata", () {