mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-30 04:39:03 +01:00
parent
742023a877
commit
be1a414f07
@ -1,3 +1,12 @@
|
||||
## 1.33.0
|
||||
|
||||
* Deprecate the use of `/` for division. The new `math.div()` function should be
|
||||
used instead. See [this page][] for details.
|
||||
|
||||
[this page]: https://sass-lang.com/documentation/breaking-changes/slash-div
|
||||
|
||||
* Add a `list.slash()` function that returns a slash-separated list.
|
||||
|
||||
## 1.32.13
|
||||
|
||||
* Use the proper parameter names in error messages about `string.slice`
|
||||
|
@ -705,6 +705,25 @@ Object /* SassString | List<Value> */ _parseChannels(
|
||||
String name, List<String> argumentNames, Value channels) {
|
||||
if (channels.isVar) return _functionString(name, [channels]);
|
||||
|
||||
var originalChannels = channels;
|
||||
Value? alphaFromSlashList;
|
||||
if (channels.separator == ListSeparator.slash) {
|
||||
var list = channels.asList;
|
||||
if (list.length != 2) {
|
||||
throw SassScriptException(
|
||||
"Only 2 slash-separated elements allowed, but ${list.length} "
|
||||
"${pluralize('was', list.length, plural: 'were')} passed.");
|
||||
}
|
||||
|
||||
channels = list[0];
|
||||
|
||||
alphaFromSlashList = list[1];
|
||||
if (!alphaFromSlashList.isSpecialNumber) {
|
||||
alphaFromSlashList.assertNumber("alpha");
|
||||
}
|
||||
if (list[0].isVar) return _functionString(name, [originalChannels]);
|
||||
}
|
||||
|
||||
var isCommaSeparated = channels.separator == ListSeparator.comma;
|
||||
var isBracketed = channels.hasBrackets;
|
||||
if (isCommaSeparated || isBracketed) {
|
||||
@ -720,18 +739,20 @@ Object /* SassString | List<Value> */ _parseChannels(
|
||||
|
||||
var list = channels.asList;
|
||||
if (list.length > 3) {
|
||||
throw SassScriptException(
|
||||
"Only 3 elements allowed, but ${list.length} were passed.");
|
||||
throw SassScriptException("Only 3 elements allowed, but ${list.length} "
|
||||
"${pluralize('was', list.length, plural: 'were')} passed.");
|
||||
} else if (list.length < 3) {
|
||||
if (list.any((value) => value.isVar) ||
|
||||
(list.isNotEmpty && _isVarSlash(list.last))) {
|
||||
return _functionString(name, [channels]);
|
||||
return _functionString(name, [originalChannels]);
|
||||
} else {
|
||||
var argument = argumentNames[list.length];
|
||||
throw SassScriptException("Missing element $argument.");
|
||||
}
|
||||
}
|
||||
|
||||
if (alphaFromSlashList != null) return [...list, alphaFromSlashList];
|
||||
|
||||
var maybeSlashSeparated = list[2];
|
||||
if (maybeSlashSeparated is SassNumber) {
|
||||
var slash = maybeSlashSeparated.asSlash;
|
||||
|
@ -20,7 +20,7 @@ final global = UnmodifiableListView([
|
||||
/// The Sass list module.
|
||||
final module = BuiltInModule("list", functions: [
|
||||
_length, _nth, _setNth, _join, _append, _zip, _index, _isBracketed, //
|
||||
_separator
|
||||
_separator, _slash
|
||||
]);
|
||||
|
||||
final _length = _function(
|
||||
@ -61,9 +61,11 @@ final _join = _function(
|
||||
separator = ListSeparator.space;
|
||||
} else if (separatorParam.text == "comma") {
|
||||
separator = ListSeparator.comma;
|
||||
} else if (separatorParam.text == "slash") {
|
||||
separator = ListSeparator.slash;
|
||||
} else {
|
||||
throw SassScriptException(
|
||||
'\$separator: Must be "space", "comma", or "auto".');
|
||||
'\$separator: Must be "space", "comma", "slash", or "auto".');
|
||||
}
|
||||
|
||||
var bracketed = bracketedParam is SassString && bracketedParam.text == 'auto'
|
||||
@ -89,9 +91,11 @@ final _append =
|
||||
separator = ListSeparator.space;
|
||||
} else if (separatorParam.text == "comma") {
|
||||
separator = ListSeparator.comma;
|
||||
} else if (separatorParam.text == "slash") {
|
||||
separator = ListSeparator.slash;
|
||||
} else {
|
||||
throw SassScriptException(
|
||||
'\$separator: Must be "space", "comma", or "auto".');
|
||||
'\$separator: Must be "space", "comma", "slash", or "auto".');
|
||||
}
|
||||
|
||||
var newList = [...list.asList, value];
|
||||
@ -121,16 +125,29 @@ final _index = _function("index", r"$list, $value", (arguments) {
|
||||
return index == -1 ? sassNull : SassNumber(index + 1);
|
||||
});
|
||||
|
||||
final _separator = _function(
|
||||
"separator",
|
||||
r"$list",
|
||||
(arguments) => arguments[0].separator == ListSeparator.comma
|
||||
? SassString("comma", quotes: false)
|
||||
: SassString("space", quotes: false));
|
||||
final _separator = _function("separator", r"$list", (arguments) {
|
||||
switch (arguments[0].separator) {
|
||||
case ListSeparator.comma:
|
||||
return SassString("comma", quotes: false);
|
||||
case ListSeparator.slash:
|
||||
return SassString("slash", quotes: false);
|
||||
default:
|
||||
return SassString("space", quotes: false);
|
||||
}
|
||||
});
|
||||
|
||||
final _isBracketed = _function("is-bracketed", r"$list",
|
||||
(arguments) => SassBoolean(arguments[0].hasBrackets));
|
||||
|
||||
final _slash = _function("slash", r"$elements...", (arguments) {
|
||||
var list = arguments[0].asList;
|
||||
if (list.length < 2) {
|
||||
throw SassScriptException("At least two elements are required.");
|
||||
}
|
||||
|
||||
return SassList(list, ListSeparator.slash);
|
||||
});
|
||||
|
||||
/// Like [new BuiltInCallable.function], but always sets the URL to `sass:list`.
|
||||
BuiltInCallable _function(
|
||||
String name, String arguments, Value callback(List<Value> arguments)) =>
|
||||
|
@ -12,6 +12,7 @@ import '../exception.dart';
|
||||
import '../module/built_in.dart';
|
||||
import '../util/number.dart';
|
||||
import '../value.dart';
|
||||
import '../warn.dart';
|
||||
|
||||
/// The global definitions of Sass math functions.
|
||||
final global = UnmodifiableListView([
|
||||
@ -25,7 +26,7 @@ final global = UnmodifiableListView([
|
||||
final module = BuiltInModule("math", functions: [
|
||||
_abs, _acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, //
|
||||
_floor, _hypot, _isUnitless, _log, _max, _min, _percentage, _pow, //
|
||||
_randomFunction, _round, _sin, _sqrt, _tan, _unit,
|
||||
_randomFunction, _round, _sin, _sqrt, _tan, _unit, _div
|
||||
], variables: {
|
||||
"e": SassNumber(math.e),
|
||||
"pi": SassNumber(math.pi),
|
||||
@ -295,6 +296,18 @@ final _randomFunction = _function("random", r"$limit: null", (arguments) {
|
||||
return SassNumber(_random.nextInt(limit) + 1);
|
||||
});
|
||||
|
||||
final _div = _function("div", r"$number1, $number2", (arguments) {
|
||||
var number1 = arguments[0];
|
||||
var number2 = arguments[1];
|
||||
|
||||
if (number1 is! SassNumber || number2 is! SassNumber) {
|
||||
warn("math.div() will only support number arguments in a future release.\n"
|
||||
"Use list.slash() instead for a slash separator.");
|
||||
}
|
||||
|
||||
return number1.dividedBy(number2);
|
||||
});
|
||||
|
||||
///
|
||||
/// Helpers
|
||||
///
|
||||
|
@ -195,27 +195,32 @@ abstract class Value implements ext.Value {
|
||||
if (list.asList.isEmpty) return null;
|
||||
|
||||
var result = <String>[];
|
||||
if (list.separator == ListSeparator.comma) {
|
||||
for (var complex in list.asList) {
|
||||
if (complex is SassString) {
|
||||
result.add(complex.text);
|
||||
} else if (complex is SassList &&
|
||||
complex.separator == ListSeparator.space) {
|
||||
var string = complex._selectorStringOrNull();
|
||||
if (string == null) return null;
|
||||
result.add(string);
|
||||
} else {
|
||||
return null;
|
||||
switch (list.separator) {
|
||||
case ListSeparator.comma:
|
||||
for (var complex in list.asList) {
|
||||
if (complex is SassString) {
|
||||
result.add(complex.text);
|
||||
} else if (complex is SassList &&
|
||||
complex.separator == ListSeparator.space) {
|
||||
var string = complex._selectorStringOrNull();
|
||||
if (string == null) return null;
|
||||
result.add(string);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var compound in list.asList) {
|
||||
if (compound is SassString) {
|
||||
result.add(compound.text);
|
||||
} else {
|
||||
return null;
|
||||
break;
|
||||
case ListSeparator.slash:
|
||||
return null;
|
||||
default:
|
||||
for (var compound in list.asList) {
|
||||
if (compound is SassString) {
|
||||
result.add(compound.text);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result.join(list.separator == ListSeparator.comma ? ', ' : ' ');
|
||||
}
|
||||
|
@ -67,6 +67,9 @@ class ListSeparator {
|
||||
/// A comma-separated list.
|
||||
static const comma = ListSeparator._("comma", ",");
|
||||
|
||||
/// A slash-separated list.
|
||||
static const slash = ListSeparator._("slash", "/");
|
||||
|
||||
/// A separator that hasn't yet been determined.
|
||||
///
|
||||
/// Singleton lists and empty lists don't have separators defined. This means
|
||||
|
@ -1135,8 +1135,8 @@ class _EvaluateVisitor
|
||||
var list = await node.list.accept(this);
|
||||
var nodeWithSpan = _expressionNode(node.list);
|
||||
var setVariables = node.variables.length == 1
|
||||
? (Value value) => _environment.setLocalVariable(
|
||||
node.variables.first, value.withoutSlash(), nodeWithSpan)
|
||||
? (Value value) => _environment.setLocalVariable(node.variables.first,
|
||||
_withoutSlash(value, nodeWithSpan), nodeWithSpan)
|
||||
: (Value value) =>
|
||||
_setMultipleVariables(node.variables, value, nodeWithSpan);
|
||||
return _environment.scope(() {
|
||||
@ -1156,7 +1156,7 @@ class _EvaluateVisitor
|
||||
var minLength = math.min(variables.length, list.length);
|
||||
for (var i = 0; i < minLength; i++) {
|
||||
_environment.setLocalVariable(
|
||||
variables[i], list[i].withoutSlash(), nodeWithSpan);
|
||||
variables[i], _withoutSlash(list[i], nodeWithSpan), nodeWithSpan);
|
||||
}
|
||||
for (var i = minLength; i < variables.length; i++) {
|
||||
_environment.setLocalVariable(variables[i], sassNull, nodeWithSpan);
|
||||
@ -1346,10 +1346,12 @@ class _EvaluateVisitor
|
||||
}
|
||||
}
|
||||
|
||||
var variableNodeWithSpan = _expressionNode(variable.expression);
|
||||
newValues[variable.name] = ConfiguredValue.explicit(
|
||||
(await variable.expression.accept(this)).withoutSlash(),
|
||||
_withoutSlash(
|
||||
await variable.expression.accept(this), variableNodeWithSpan),
|
||||
variable.span,
|
||||
_expressionNode(variable.expression));
|
||||
variableNodeWithSpan);
|
||||
}
|
||||
|
||||
if (configuration is ExplicitConfiguration || configuration.isEmpty) {
|
||||
@ -1761,8 +1763,8 @@ class _EvaluateVisitor
|
||||
return queries;
|
||||
}
|
||||
|
||||
Future<Value> visitReturnRule(ReturnRule node) =>
|
||||
node.expression.accept(this);
|
||||
Future<Value> visitReturnRule(ReturnRule node) async =>
|
||||
_withoutSlash(await node.expression.accept(this), node.expression);
|
||||
|
||||
Future<Value?> visitSilentComment(SilentComment node) async => null;
|
||||
|
||||
@ -1949,7 +1951,8 @@ class _EvaluateVisitor
|
||||
deprecation: true);
|
||||
}
|
||||
|
||||
var value = (await node.expression.accept(this)).withoutSlash();
|
||||
var value =
|
||||
_withoutSlash(await node.expression.accept(this), node.expression);
|
||||
_addExceptionSpan(node, () {
|
||||
_environment.setVariable(
|
||||
node.name, value, _expressionNode(node.expression),
|
||||
@ -1959,15 +1962,19 @@ class _EvaluateVisitor
|
||||
}
|
||||
|
||||
Future<Value?> visitUseRule(UseRule node) async {
|
||||
var configuration = node.configuration.isEmpty
|
||||
? const Configuration.empty()
|
||||
: ExplicitConfiguration({
|
||||
for (var variable in node.configuration)
|
||||
variable.name: ConfiguredValue.explicit(
|
||||
(await variable.expression.accept(this)).withoutSlash(),
|
||||
variable.span,
|
||||
_expressionNode(variable.expression))
|
||||
}, node);
|
||||
var configuration = const Configuration.empty();
|
||||
if (node.configuration.isNotEmpty) {
|
||||
var values = <String, ConfiguredValue>{};
|
||||
for (var variable in node.configuration) {
|
||||
var variableNodeWithSpan = _expressionNode(variable.expression);
|
||||
values[variable.name] = ConfiguredValue.explicit(
|
||||
_withoutSlash(
|
||||
await variable.expression.accept(this), variableNodeWithSpan),
|
||||
variable.span,
|
||||
variableNodeWithSpan);
|
||||
}
|
||||
configuration = ExplicitConfiguration(values, node);
|
||||
}
|
||||
|
||||
await _loadModule(node.url, "@use", node, (module) {
|
||||
_environment.addModule(module, node, namespace: node.namespace);
|
||||
@ -2055,6 +2062,29 @@ class _EvaluateVisitor
|
||||
if (node.allowsSlash && left is SassNumber && right is SassNumber) {
|
||||
return (result as SassNumber).withSlash(left, right);
|
||||
} else {
|
||||
if (left is SassNumber && right is SassNumber) {
|
||||
String recommendation(Expression expression) {
|
||||
if (expression is BinaryOperationExpression &&
|
||||
expression.operator == BinaryOperator.dividedBy) {
|
||||
return "math.div(${recommendation(expression.left)}, "
|
||||
"${recommendation(expression.right)})";
|
||||
} else {
|
||||
return expression.toString();
|
||||
}
|
||||
}
|
||||
|
||||
_warn(
|
||||
"Using / for division is deprecated and will be removed in "
|
||||
"Dart Sass 2.0.0.\n"
|
||||
"\n"
|
||||
"Recommendation: ${recommendation(node)}\n"
|
||||
"\n"
|
||||
"More info and automated migrator: "
|
||||
"https://sass-lang.com/d/slash-div",
|
||||
node.span,
|
||||
deprecation: true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -2199,6 +2229,8 @@ class _EvaluateVisitor
|
||||
UserDefinedCallable<AsyncEnvironment> callable,
|
||||
AstNode nodeWithSpan,
|
||||
Future<V> run()) async {
|
||||
// TODO(nweiz): Set [trackSpans] to `null` once we're no longer emitting
|
||||
// deprecation warnings for /-as-division.
|
||||
var evaluated = await _evaluateArguments(arguments);
|
||||
|
||||
var name = callable.name;
|
||||
@ -2216,10 +2248,11 @@ class _EvaluateVisitor
|
||||
var minLength =
|
||||
math.min(evaluated.positional.length, declaredArguments.length);
|
||||
for (var i = 0; i < minLength; i++) {
|
||||
var nodeForSpan = evaluated.positionalNodes[i];
|
||||
_environment.setLocalVariable(
|
||||
declaredArguments[i].name,
|
||||
evaluated.positional[i].withoutSlash(),
|
||||
evaluated.positionalNodes[i]);
|
||||
_withoutSlash(evaluated.positional[i], nodeForSpan),
|
||||
nodeForSpan);
|
||||
}
|
||||
|
||||
for (var i = evaluated.positional.length;
|
||||
@ -2228,11 +2261,10 @@ class _EvaluateVisitor
|
||||
var argument = declaredArguments[i];
|
||||
var value = evaluated.named.remove(argument.name) ??
|
||||
await argument.defaultValue!.accept<Future<Value>>(this);
|
||||
var nodeForSpan = evaluated.namedNodes[argument.name] ??
|
||||
_expressionNode(argument.defaultValue!);
|
||||
_environment.setLocalVariable(
|
||||
argument.name,
|
||||
value.withoutSlash(),
|
||||
evaluated.namedNodes[argument.name] ??
|
||||
_expressionNode(argument.defaultValue!));
|
||||
argument.name, _withoutSlash(value, nodeForSpan), nodeForSpan);
|
||||
}
|
||||
|
||||
SassArgumentList? argumentList;
|
||||
@ -2275,11 +2307,12 @@ class _EvaluateVisitor
|
||||
Future<Value> _runFunctionCallable(ArgumentInvocation arguments,
|
||||
AsyncCallable? callable, AstNode nodeWithSpan) async {
|
||||
if (callable is AsyncBuiltInCallable) {
|
||||
return (await _runBuiltInCallable(arguments, callable, nodeWithSpan))
|
||||
.withoutSlash();
|
||||
return _withoutSlash(
|
||||
await _runBuiltInCallable(arguments, callable, nodeWithSpan),
|
||||
nodeWithSpan);
|
||||
} else if (callable is UserDefinedCallable<AsyncEnvironment>) {
|
||||
return (await _runUserDefinedCallable(arguments, callable, nodeWithSpan,
|
||||
() async {
|
||||
return await _runUserDefinedCallable(arguments, callable, nodeWithSpan,
|
||||
() async {
|
||||
for (var statement in callable.declaration.children) {
|
||||
var returnValue = await statement.accept(this);
|
||||
if (returnValue is Value) return returnValue;
|
||||
@ -2287,8 +2320,7 @@ class _EvaluateVisitor
|
||||
|
||||
throw _exception(
|
||||
"Function finished without @return.", callable.declaration.span);
|
||||
}))
|
||||
.withoutSlash();
|
||||
});
|
||||
} else if (callable is PlainCssCallable) {
|
||||
if (arguments.named.isNotEmpty || arguments.keywordRest != null) {
|
||||
throw _exception("Plain CSS functions don't support keyword arguments.",
|
||||
@ -2880,9 +2912,9 @@ class _EvaluateVisitor
|
||||
|
||||
/// Returns the [AstNode] whose span should be used for [expression].
|
||||
///
|
||||
/// If [expression] is a variable reference, [AstNode]'s span will be the span
|
||||
/// where that variable was originally declared. Otherwise, this will just
|
||||
/// return [expression].
|
||||
/// If [expression] is a variable reference and [_sourceMap] is `true`,
|
||||
/// [AstNode]'s span will be the span where that variable was originally
|
||||
/// declared. Otherwise, this will just return [expression].
|
||||
///
|
||||
/// This returns an [AstNode] rather than a [FileSpan] so we can avoid calling
|
||||
/// [AstNode.span] if the span isn't required, since some nodes need to do
|
||||
@ -2894,8 +2926,10 @@ class _EvaluateVisitor
|
||||
// are gone we should go back to short-circuiting.
|
||||
|
||||
if (expression is VariableExpression) {
|
||||
return _environment.getVariableNode(expression.name,
|
||||
namespace: expression.namespace) ??
|
||||
return _addExceptionSpan(
|
||||
expression,
|
||||
() => _environment.getVariableNode(expression.name,
|
||||
namespace: expression.namespace)) ??
|
||||
expression;
|
||||
} else {
|
||||
return expression;
|
||||
@ -2994,6 +3028,35 @@ class _EvaluateVisitor
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Like [Value.withoutSlash], but produces a deprecation warning if [value]
|
||||
/// was a slash-separated number.
|
||||
Value _withoutSlash(Value value, AstNode nodeForSpan) {
|
||||
if (value is SassNumber && value.asSlash != null) {
|
||||
String recommendation(SassNumber number) {
|
||||
var asSlash = number.asSlash;
|
||||
if (asSlash != null) {
|
||||
return "math.div(${recommendation(asSlash.item1)}, "
|
||||
"${recommendation(asSlash.item2)})";
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
||||
|
||||
_warn(
|
||||
"Using / for division is deprecated and will be removed in Dart Sass "
|
||||
"2.0.0.\n"
|
||||
"\n"
|
||||
"Recommendation: ${recommendation(value)}\n"
|
||||
"\n"
|
||||
"More info and automated migrator: "
|
||||
"https://sass-lang.com/d/slash-div",
|
||||
nodeForSpan.span,
|
||||
deprecation: true);
|
||||
}
|
||||
|
||||
return value.withoutSlash();
|
||||
}
|
||||
|
||||
/// Creates a new stack frame with location information from [member] and
|
||||
/// [span].
|
||||
Frame _stackFrame(String member, FileSpan span) => frameForSpan(span, member,
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_evaluate.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 6351ed2d303a58943ce6be39dc794fb46286fd64
|
||||
// Checksum: 3e8746ad4514a9aff33701f3ca4fa02f3594fb3a
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -1139,8 +1139,8 @@ class _EvaluateVisitor
|
||||
var list = node.list.accept(this);
|
||||
var nodeWithSpan = _expressionNode(node.list);
|
||||
var setVariables = node.variables.length == 1
|
||||
? (Value value) => _environment.setLocalVariable(
|
||||
node.variables.first, value.withoutSlash(), nodeWithSpan)
|
||||
? (Value value) => _environment.setLocalVariable(node.variables.first,
|
||||
_withoutSlash(value, nodeWithSpan), nodeWithSpan)
|
||||
: (Value value) =>
|
||||
_setMultipleVariables(node.variables, value, nodeWithSpan);
|
||||
return _environment.scope(() {
|
||||
@ -1160,7 +1160,7 @@ class _EvaluateVisitor
|
||||
var minLength = math.min(variables.length, list.length);
|
||||
for (var i = 0; i < minLength; i++) {
|
||||
_environment.setLocalVariable(
|
||||
variables[i], list[i].withoutSlash(), nodeWithSpan);
|
||||
variables[i], _withoutSlash(list[i], nodeWithSpan), nodeWithSpan);
|
||||
}
|
||||
for (var i = minLength; i < variables.length; i++) {
|
||||
_environment.setLocalVariable(variables[i], sassNull, nodeWithSpan);
|
||||
@ -1347,10 +1347,11 @@ class _EvaluateVisitor
|
||||
}
|
||||
}
|
||||
|
||||
var variableNodeWithSpan = _expressionNode(variable.expression);
|
||||
newValues[variable.name] = ConfiguredValue.explicit(
|
||||
variable.expression.accept(this).withoutSlash(),
|
||||
_withoutSlash(variable.expression.accept(this), variableNodeWithSpan),
|
||||
variable.span,
|
||||
_expressionNode(variable.expression));
|
||||
variableNodeWithSpan);
|
||||
}
|
||||
|
||||
if (configuration is ExplicitConfiguration || configuration.isEmpty) {
|
||||
@ -1756,7 +1757,8 @@ class _EvaluateVisitor
|
||||
return queries;
|
||||
}
|
||||
|
||||
Value visitReturnRule(ReturnRule node) => node.expression.accept(this);
|
||||
Value visitReturnRule(ReturnRule node) =>
|
||||
_withoutSlash(node.expression.accept(this), node.expression);
|
||||
|
||||
Value? visitSilentComment(SilentComment node) => null;
|
||||
|
||||
@ -1941,7 +1943,7 @@ class _EvaluateVisitor
|
||||
deprecation: true);
|
||||
}
|
||||
|
||||
var value = node.expression.accept(this).withoutSlash();
|
||||
var value = _withoutSlash(node.expression.accept(this), node.expression);
|
||||
_addExceptionSpan(node, () {
|
||||
_environment.setVariable(
|
||||
node.name, value, _expressionNode(node.expression),
|
||||
@ -1951,15 +1953,19 @@ class _EvaluateVisitor
|
||||
}
|
||||
|
||||
Value? visitUseRule(UseRule node) {
|
||||
var configuration = node.configuration.isEmpty
|
||||
? const Configuration.empty()
|
||||
: ExplicitConfiguration({
|
||||
for (var variable in node.configuration)
|
||||
variable.name: ConfiguredValue.explicit(
|
||||
variable.expression.accept(this).withoutSlash(),
|
||||
variable.span,
|
||||
_expressionNode(variable.expression))
|
||||
}, node);
|
||||
var configuration = const Configuration.empty();
|
||||
if (node.configuration.isNotEmpty) {
|
||||
var values = <String, ConfiguredValue>{};
|
||||
for (var variable in node.configuration) {
|
||||
var variableNodeWithSpan = _expressionNode(variable.expression);
|
||||
values[variable.name] = ConfiguredValue.explicit(
|
||||
_withoutSlash(
|
||||
variable.expression.accept(this), variableNodeWithSpan),
|
||||
variable.span,
|
||||
variableNodeWithSpan);
|
||||
}
|
||||
configuration = ExplicitConfiguration(values, node);
|
||||
}
|
||||
|
||||
_loadModule(node.url, "@use", node, (module) {
|
||||
_environment.addModule(module, node, namespace: node.namespace);
|
||||
@ -2046,6 +2052,29 @@ class _EvaluateVisitor
|
||||
if (node.allowsSlash && left is SassNumber && right is SassNumber) {
|
||||
return (result as SassNumber).withSlash(left, right);
|
||||
} else {
|
||||
if (left is SassNumber && right is SassNumber) {
|
||||
String recommendation(Expression expression) {
|
||||
if (expression is BinaryOperationExpression &&
|
||||
expression.operator == BinaryOperator.dividedBy) {
|
||||
return "math.div(${recommendation(expression.left)}, "
|
||||
"${recommendation(expression.right)})";
|
||||
} else {
|
||||
return expression.toString();
|
||||
}
|
||||
}
|
||||
|
||||
_warn(
|
||||
"Using / for division is deprecated and will be removed in "
|
||||
"Dart Sass 2.0.0.\n"
|
||||
"\n"
|
||||
"Recommendation: ${recommendation(node)}\n"
|
||||
"\n"
|
||||
"More info and automated migrator: "
|
||||
"https://sass-lang.com/d/slash-div",
|
||||
node.span,
|
||||
deprecation: true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -2186,6 +2215,8 @@ class _EvaluateVisitor
|
||||
UserDefinedCallable<Environment> callable,
|
||||
AstNode nodeWithSpan,
|
||||
V run()) {
|
||||
// TODO(nweiz): Set [trackSpans] to `null` once we're no longer emitting
|
||||
// deprecation warnings for /-as-division.
|
||||
var evaluated = _evaluateArguments(arguments);
|
||||
|
||||
var name = callable.name;
|
||||
@ -2203,10 +2234,11 @@ class _EvaluateVisitor
|
||||
var minLength =
|
||||
math.min(evaluated.positional.length, declaredArguments.length);
|
||||
for (var i = 0; i < minLength; i++) {
|
||||
var nodeForSpan = evaluated.positionalNodes[i];
|
||||
_environment.setLocalVariable(
|
||||
declaredArguments[i].name,
|
||||
evaluated.positional[i].withoutSlash(),
|
||||
evaluated.positionalNodes[i]);
|
||||
_withoutSlash(evaluated.positional[i], nodeForSpan),
|
||||
nodeForSpan);
|
||||
}
|
||||
|
||||
for (var i = evaluated.positional.length;
|
||||
@ -2215,11 +2247,10 @@ class _EvaluateVisitor
|
||||
var argument = declaredArguments[i];
|
||||
var value = evaluated.named.remove(argument.name) ??
|
||||
argument.defaultValue!.accept<Value>(this);
|
||||
var nodeForSpan = evaluated.namedNodes[argument.name] ??
|
||||
_expressionNode(argument.defaultValue!);
|
||||
_environment.setLocalVariable(
|
||||
argument.name,
|
||||
value.withoutSlash(),
|
||||
evaluated.namedNodes[argument.name] ??
|
||||
_expressionNode(argument.defaultValue!));
|
||||
argument.name, _withoutSlash(value, nodeForSpan), nodeForSpan);
|
||||
}
|
||||
|
||||
SassArgumentList? argumentList;
|
||||
@ -2262,8 +2293,8 @@ class _EvaluateVisitor
|
||||
Value _runFunctionCallable(
|
||||
ArgumentInvocation arguments, Callable? callable, AstNode nodeWithSpan) {
|
||||
if (callable is BuiltInCallable) {
|
||||
return _runBuiltInCallable(arguments, callable, nodeWithSpan)
|
||||
.withoutSlash();
|
||||
return _withoutSlash(
|
||||
_runBuiltInCallable(arguments, callable, nodeWithSpan), nodeWithSpan);
|
||||
} else if (callable is UserDefinedCallable<Environment>) {
|
||||
return _runUserDefinedCallable(arguments, callable, nodeWithSpan, () {
|
||||
for (var statement in callable.declaration.children) {
|
||||
@ -2273,7 +2304,7 @@ class _EvaluateVisitor
|
||||
|
||||
throw _exception(
|
||||
"Function finished without @return.", callable.declaration.span);
|
||||
}).withoutSlash();
|
||||
});
|
||||
} else if (callable is PlainCssCallable) {
|
||||
if (arguments.named.isNotEmpty || arguments.keywordRest != null) {
|
||||
throw _exception("Plain CSS functions don't support keyword arguments.",
|
||||
@ -2857,9 +2888,9 @@ class _EvaluateVisitor
|
||||
|
||||
/// Returns the [AstNode] whose span should be used for [expression].
|
||||
///
|
||||
/// If [expression] is a variable reference, [AstNode]'s span will be the span
|
||||
/// where that variable was originally declared. Otherwise, this will just
|
||||
/// return [expression].
|
||||
/// If [expression] is a variable reference and [_sourceMap] is `true`,
|
||||
/// [AstNode]'s span will be the span where that variable was originally
|
||||
/// declared. Otherwise, this will just return [expression].
|
||||
///
|
||||
/// This returns an [AstNode] rather than a [FileSpan] so we can avoid calling
|
||||
/// [AstNode.span] if the span isn't required, since some nodes need to do
|
||||
@ -2871,8 +2902,10 @@ class _EvaluateVisitor
|
||||
// are gone we should go back to short-circuiting.
|
||||
|
||||
if (expression is VariableExpression) {
|
||||
return _environment.getVariableNode(expression.name,
|
||||
namespace: expression.namespace) ??
|
||||
return _addExceptionSpan(
|
||||
expression,
|
||||
() => _environment.getVariableNode(expression.name,
|
||||
namespace: expression.namespace)) ??
|
||||
expression;
|
||||
} else {
|
||||
return expression;
|
||||
@ -2967,6 +3000,35 @@ class _EvaluateVisitor
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Like [Value.withoutSlash], but produces a deprecation warning if [value]
|
||||
/// was a slash-separated number.
|
||||
Value _withoutSlash(Value value, AstNode nodeForSpan) {
|
||||
if (value is SassNumber && value.asSlash != null) {
|
||||
String recommendation(SassNumber number) {
|
||||
var asSlash = number.asSlash;
|
||||
if (asSlash != null) {
|
||||
return "math.div(${recommendation(asSlash.item1)}, "
|
||||
"${recommendation(asSlash.item2)})";
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
||||
|
||||
_warn(
|
||||
"Using / for division is deprecated and will be removed in Dart Sass "
|
||||
"2.0.0.\n"
|
||||
"\n"
|
||||
"Recommendation: ${recommendation(value)}\n"
|
||||
"\n"
|
||||
"More info and automated migrator: "
|
||||
"https://sass-lang.com/d/slash-div",
|
||||
nodeForSpan.span,
|
||||
deprecation: true);
|
||||
}
|
||||
|
||||
return value.withoutSlash();
|
||||
}
|
||||
|
||||
/// Creates a new stack frame with location information from [member] and
|
||||
/// [span].
|
||||
Frame _stackFrame(String member, FileSpan span) => frameForSpan(span, member,
|
||||
|
@ -559,14 +559,15 @@ class _SerializeVisitor
|
||||
|
||||
var singleton = _inspect &&
|
||||
value.asList.length == 1 &&
|
||||
value.separator == ListSeparator.comma;
|
||||
(value.separator == ListSeparator.comma ||
|
||||
value.separator == ListSeparator.slash);
|
||||
if (singleton && !value.hasBrackets) _buffer.writeCharCode($lparen);
|
||||
|
||||
_writeBetween<Value>(
|
||||
_inspect
|
||||
? value.asList
|
||||
: value.asList.where((element) => !element.isBlank),
|
||||
value.separator == ListSeparator.space ? " " : _commaSeparator,
|
||||
_separatorString(value.separator),
|
||||
_inspect
|
||||
? (element) {
|
||||
var needsParens = _elementNeedsParens(value.separator, element);
|
||||
@ -579,22 +580,45 @@ class _SerializeVisitor
|
||||
});
|
||||
|
||||
if (singleton) {
|
||||
_buffer.writeCharCode($comma);
|
||||
_buffer.write(value.separator.separator);
|
||||
if (!value.hasBrackets) _buffer.writeCharCode($rparen);
|
||||
}
|
||||
|
||||
if (value.hasBrackets) _buffer.writeCharCode($rbracket);
|
||||
}
|
||||
|
||||
/// Returns the string to use to separate list items for lists with the given [separator].
|
||||
String _separatorString(ListSeparator separator) {
|
||||
switch (separator) {
|
||||
case ListSeparator.comma:
|
||||
return _commaSeparator;
|
||||
case ListSeparator.slash:
|
||||
return _isCompressed ? "/" : " / ";
|
||||
case ListSeparator.space:
|
||||
return " ";
|
||||
default:
|
||||
// This should never be used, but it may still be returned since
|
||||
// [_separatorString] is invoked eagerly by [writeList] even for lists
|
||||
// with only one elements.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether [value] needs parentheses as an element in a list with the
|
||||
/// given [separator].
|
||||
bool _elementNeedsParens(ListSeparator separator, Value value) {
|
||||
if (value is SassList) {
|
||||
if (value.asList.length < 2) return false;
|
||||
if (value.hasBrackets) return false;
|
||||
return separator == ListSeparator.comma
|
||||
? value.separator == ListSeparator.comma
|
||||
: value.separator != ListSeparator.undecided;
|
||||
switch (separator) {
|
||||
case ListSeparator.comma:
|
||||
return value.separator == ListSeparator.comma;
|
||||
case ListSeparator.slash:
|
||||
return value.separator == ListSeparator.comma ||
|
||||
value.separator == ListSeparator.slash;
|
||||
default:
|
||||
return value.separator != ListSeparator.undecided;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: sass
|
||||
version: 1.32.13-dev
|
||||
version: 1.33.0
|
||||
description: A Sass implementation in Dart.
|
||||
author: Sass Team
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
@ -109,6 +109,13 @@ void main() {
|
||||
expect(_compile("a {b: x, y, z}"), equals("a{b:x,y,z}"));
|
||||
});
|
||||
|
||||
test("don't include spaces around slashes", () {
|
||||
expect(_compile("""
|
||||
@use "sass:list";
|
||||
a {b: list.slash(x, y, z)}
|
||||
"""), equals("a{b:x/y/z}"));
|
||||
});
|
||||
|
||||
test("do include spaces when space-separated", () {
|
||||
expect(_compile("a {b: x y z}"), equals("a{b:x y z}"));
|
||||
});
|
||||
|
@ -116,6 +116,11 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
test("a slash-separated list is space-separated", () {
|
||||
expect(parseValue("list.slash(a, b, c)").separator,
|
||||
equals(ListSeparator.slash));
|
||||
});
|
||||
|
||||
test("a space-separated list is space-separated", () {
|
||||
expect(parseValue("a, b, c").separator, equals(ListSeparator.comma));
|
||||
});
|
||||
|
@ -10,7 +10,11 @@ import 'package:sass/src/exception.dart';
|
||||
/// Parses [source] by way of a function call.
|
||||
Value parseValue(String source) {
|
||||
late Value value;
|
||||
compileString("a {b: foo(($source))}", functions: [
|
||||
compileString("""
|
||||
@use "sass:list";
|
||||
|
||||
a {b: foo(($source))}
|
||||
""", functions: [
|
||||
Callable("foo", r"$arg", expectAsync1((arguments) {
|
||||
expect(arguments, hasLength(1));
|
||||
value = arguments.first;
|
||||
|
Loading…
Reference in New Issue
Block a user