Skip to content

Commit f38dbb0

Browse files
authored
Merge pull request #2464 from sass/rest-param-comma
Allow a trailing comma after rest parameters and arguments
2 parents 219fe67 + 0230ccf commit f38dbb0

39 files changed

+338
-346
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
## 1.82.1-dev
1+
## 1.83.0
22

3-
* No user-visible changes.
3+
* Allow trailing commas in *all* argument and parameter lists.
44

55
## 1.82.0
66

lib/src/ast/sass.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5-
export 'sass/argument.dart';
6-
export 'sass/argument_declaration.dart';
7-
export 'sass/argument_invocation.dart';
5+
export 'sass/argument_list.dart';
86
export 'sass/at_root_query.dart';
97
export 'sass/callable_invocation.dart';
108
export 'sass/configured_variable.dart';
@@ -33,6 +31,8 @@ export 'sass/import/dynamic.dart';
3331
export 'sass/import/static.dart';
3432
export 'sass/interpolation.dart';
3533
export 'sass/node.dart';
34+
export 'sass/parameter.dart';
35+
export 'sass/parameter_list.dart';
3636
export 'sass/reference.dart';
3737
export 'sass/statement.dart';
3838
export 'sass/statement/at_root_rule.dart';

lib/src/ast/sass/argument_invocation.dart renamed to lib/src/ast/sass/argument_list.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import 'node.dart';
1313
/// A set of arguments passed in to a function or mixin.
1414
///
1515
/// {@category AST}
16-
final class ArgumentInvocation implements SassNode {
16+
final class ArgumentList implements SassNode {
1717
/// The arguments passed by position.
1818
final List<Expression> positional;
1919

@@ -31,7 +31,7 @@ final class ArgumentInvocation implements SassNode {
3131
/// Returns whether this invocation passes no arguments.
3232
bool get isEmpty => positional.isEmpty && named.isEmpty && rest == null;
3333

34-
ArgumentInvocation(
34+
ArgumentList(
3535
Iterable<Expression> positional, Map<String, Expression> named, this.span,
3636
{this.rest, this.keywordRest})
3737
: positional = List.unmodifiable(positional),
@@ -40,7 +40,7 @@ final class ArgumentInvocation implements SassNode {
4040
}
4141

4242
/// Creates an invocation that passes no arguments.
43-
ArgumentInvocation.empty(this.span)
43+
ArgumentList.empty(this.span)
4444
: positional = const [],
4545
named = const {},
4646
rest = null,

lib/src/ast/sass/callable_invocation.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import 'package:meta/meta.dart';
66

7-
import 'argument_invocation.dart';
7+
import 'argument_list.dart';
88
import 'node.dart';
99

1010
/// An abstract class for invoking a callable (a function or mixin).
@@ -13,5 +13,5 @@ import 'node.dart';
1313
@sealed
1414
abstract class CallableInvocation implements SassNode {
1515
/// The arguments passed to the callable.
16-
ArgumentInvocation get arguments;
16+
ArgumentList get arguments;
1717
}

lib/src/ast/sass/expression/function.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:source_span/source_span.dart';
77
import '../../../util/span.dart';
88
import '../../../visitor/interface/expression.dart';
99
import '../expression.dart';
10-
import '../argument_invocation.dart';
10+
import '../argument_list.dart';
1111
import '../callable_invocation.dart';
1212
import '../reference.dart';
1313

@@ -33,7 +33,7 @@ final class FunctionExpression extends Expression
3333
final String originalName;
3434

3535
/// The arguments to pass to the function.
36-
final ArgumentInvocation arguments;
36+
final ArgumentList arguments;
3737

3838
final FileSpan span;
3939

lib/src/ast/sass/expression/if.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import '../../../visitor/interface/expression.dart';
1616
/// {@category AST}
1717
final class IfExpression extends Expression implements CallableInvocation {
1818
/// The declaration of `if()`, as though it were a normal function.
19-
static final declaration = ArgumentDeclaration.parse(
20-
r"@function if($condition, $if-true, $if-false) {");
19+
static final declaration =
20+
ParameterList.parse(r"@function if($condition, $if-true, $if-false) {");
2121

2222
/// The arguments passed to `if()`.
23-
final ArgumentInvocation arguments;
23+
final ArgumentList arguments;
2424

2525
final FileSpan span;
2626

lib/src/ast/sass/expression/interpolated_function.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'package:source_span/source_span.dart';
66

77
import '../../../visitor/interface/expression.dart';
88
import '../expression.dart';
9-
import '../argument_invocation.dart';
9+
import '../argument_list.dart';
1010
import '../callable_invocation.dart';
1111
import '../interpolation.dart';
1212

@@ -21,7 +21,7 @@ final class InterpolatedFunctionExpression extends Expression
2121
final Interpolation name;
2222

2323
/// The arguments to pass to the function.
24-
final ArgumentInvocation arguments;
24+
final ArgumentList arguments;
2525

2626
final FileSpan span;
2727

lib/src/ast/sass/argument.dart renamed to lib/src/ast/sass/parameter.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import 'expression.dart';
1010
import 'declaration.dart';
1111
import 'node.dart';
1212

13-
/// An argument declared as part of an [ArgumentDeclaration].
13+
/// An parameter declared as part of an [ParameterList].
1414
///
1515
/// {@category AST}
16-
final class Argument implements SassNode, SassDeclaration {
17-
/// The argument name.
16+
final class Parameter implements SassNode, SassDeclaration {
17+
/// The parameter name.
1818
final String name;
1919

20-
/// The default value of this argument, or `null` if none was declared.
20+
/// The default value of this parameter, or `null` if none was declared.
2121
final Expression? defaultValue;
2222

2323
final FileSpan span;
@@ -33,7 +33,7 @@ final class Argument implements SassNode, SassDeclaration {
3333
FileSpan get nameSpan =>
3434
defaultValue == null ? span : span.initialIdentifier(includeLeading: 1);
3535

36-
Argument(this.name, this.span, {this.defaultValue});
36+
Parameter(this.name, this.span, {this.defaultValue});
3737

3838
String toString() => defaultValue == null ? name : "$name: $defaultValue";
3939
}

lib/src/ast/sass/argument_declaration.dart renamed to lib/src/ast/sass/parameter_list.dart

Lines changed: 50 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import '../../parse/scss.dart';
99
import '../../util/character.dart';
1010
import '../../util/span.dart';
1111
import '../../utils.dart';
12-
import 'argument.dart';
12+
import 'parameter.dart';
1313
import 'node.dart';
1414

15-
/// An argument declaration, as for a function or mixin definition.
15+
/// An parameter declaration, as for a function or mixin definition.
1616
///
1717
/// {@category AST}
1818
/// {@category Parsing}
19-
final class ArgumentDeclaration implements SassNode {
20-
/// The arguments that are taken.
21-
final List<Argument> arguments;
19+
final class ParameterList implements SassNode {
20+
/// The parameters that are taken.
21+
final List<Parameter> parameters;
2222

23-
/// The name of the rest argument (as in `$args...`), or `null` if none was
23+
/// The name of the rest parameter (as in `$args...`), or `null` if none was
2424
/// declared.
25-
final String? restArgument;
25+
final String? restParameter;
2626

2727
final FileSpan span;
2828

@@ -31,7 +31,7 @@ final class ArgumentDeclaration implements SassNode {
3131
FileSpan get spanWithName {
3232
var text = span.file.getText(0);
3333

34-
// Move backwards through any whitespace between the name and the arguments.
34+
// Move backwards through any whitespace between the name and the parameters.
3535
var i = span.start.offset - 1;
3636
while (i > 0 && text.codeUnitAt(i).isWhitespace) {
3737
i--;
@@ -48,60 +48,59 @@ final class ArgumentDeclaration implements SassNode {
4848
if (!text.codeUnitAt(i + 1).isNameStart) return span;
4949

5050
// Trim because it's possible that this span is empty (for example, a mixin
51-
// may be declared without an argument list).
51+
// may be declared without an parameter list).
5252
return span.file.span(i + 1, span.end.offset).trim();
5353
}
5454

55-
/// Returns whether this declaration takes no arguments.
56-
bool get isEmpty => arguments.isEmpty && restArgument == null;
55+
/// Returns whether this declaration takes no parameters.
56+
bool get isEmpty => parameters.isEmpty && restParameter == null;
5757

58-
ArgumentDeclaration(Iterable<Argument> arguments, this.span,
59-
{this.restArgument})
60-
: arguments = List.unmodifiable(arguments);
58+
ParameterList(Iterable<Parameter> parameters, this.span, {this.restParameter})
59+
: parameters = List.unmodifiable(parameters);
6160

62-
/// Creates a declaration that declares no arguments.
63-
ArgumentDeclaration.empty(this.span)
64-
: arguments = const [],
65-
restArgument = null;
61+
/// Creates a declaration that declares no parameters.
62+
ParameterList.empty(this.span)
63+
: parameters = const [],
64+
restParameter = null;
6665

67-
/// Parses an argument declaration from [contents], which should be of the
66+
/// Parses an parameter declaration from [contents], which should be of the
6867
/// form `@rule name(args) {`.
6968
///
7069
/// If passed, [url] is the name of the file from which [contents] comes.
7170
///
7271
/// Throws a [SassFormatException] if parsing fails.
73-
factory ArgumentDeclaration.parse(String contents, {Object? url}) =>
74-
ScssParser(contents, url: url).parseArgumentDeclaration();
72+
factory ParameterList.parse(String contents, {Object? url}) =>
73+
ScssParser(contents, url: url).parseParameterList();
7574

7675
/// Throws a [SassScriptException] if [positional] and [names] aren't valid
77-
/// for this argument declaration.
76+
/// for this parameter declaration.
7877
void verify(int positional, Set<String> names) {
7978
var namedUsed = 0;
80-
for (var i = 0; i < arguments.length; i++) {
81-
var argument = arguments[i];
79+
for (var i = 0; i < parameters.length; i++) {
80+
var parameter = parameters[i];
8281
if (i < positional) {
83-
if (names.contains(argument.name)) {
82+
if (names.contains(parameter.name)) {
8483
throw SassScriptException(
85-
"Argument ${_originalArgumentName(argument.name)} was passed "
84+
"Argument ${_originalParameterName(parameter.name)} was passed "
8685
"both by position and by name.");
8786
}
88-
} else if (names.contains(argument.name)) {
87+
} else if (names.contains(parameter.name)) {
8988
namedUsed++;
90-
} else if (argument.defaultValue == null) {
89+
} else if (parameter.defaultValue == null) {
9190
throw MultiSpanSassScriptException(
92-
"Missing argument ${_originalArgumentName(argument.name)}.",
91+
"Missing argument ${_originalParameterName(parameter.name)}.",
9392
"invocation",
9493
{spanWithName: "declaration"});
9594
}
9695
}
9796

98-
if (restArgument != null) return;
97+
if (restParameter != null) return;
9998

100-
if (positional > arguments.length) {
99+
if (positional > parameters.length) {
101100
throw MultiSpanSassScriptException(
102-
"Only ${arguments.length} "
101+
"Only ${parameters.length} "
103102
"${names.isEmpty ? '' : 'positional '}"
104-
"${pluralize('argument', arguments.length)} allowed, but "
103+
"${pluralize('argument', parameters.length)} allowed, but "
105104
"$positional ${pluralize('was', positional, plural: 'were')} "
106105
"passed.",
107106
"invocation",
@@ -110,54 +109,54 @@ final class ArgumentDeclaration implements SassNode {
110109

111110
if (namedUsed < names.length) {
112111
var unknownNames = Set.of(names)
113-
..removeAll(arguments.map((argument) => argument.name));
112+
..removeAll(parameters.map((parameter) => parameter.name));
114113
throw MultiSpanSassScriptException(
115-
"No ${pluralize('argument', unknownNames.length)} named "
114+
"No ${pluralize('parameter', unknownNames.length)} named "
116115
"${toSentence(unknownNames.map((name) => "\$$name"), 'or')}.",
117116
"invocation",
118117
{spanWithName: "declaration"});
119118
}
120119
}
121120

122-
/// Returns the argument named [name] with a leading `$` and its original
121+
/// Returns the parameter named [name] with a leading `$` and its original
123122
/// underscores (which are otherwise converted to hyphens).
124-
String _originalArgumentName(String name) {
125-
if (name == restArgument) {
123+
String _originalParameterName(String name) {
124+
if (name == restParameter) {
126125
var text = span.text;
127126
var fromDollar = text.substring(text.lastIndexOf("\$"));
128127
return fromDollar.substring(0, text.indexOf("."));
129128
}
130129

131-
for (var argument in arguments) {
132-
if (argument.name == name) return argument.originalName;
130+
for (var parameter in parameters) {
131+
if (parameter.name == name) return parameter.originalName;
133132
}
134133

135-
throw ArgumentError('This declaration has no argument named "\$$name".');
134+
throw ArgumentError('This declaration has no parameter named "\$$name".');
136135
}
137136

138-
/// Returns whether [positional] and [names] are valid for this argument
137+
/// Returns whether [positional] and [names] are valid for this parameter
139138
/// declaration.
140139
bool matches(int positional, Set<String> names) {
141140
var namedUsed = 0;
142-
for (var i = 0; i < arguments.length; i++) {
143-
var argument = arguments[i];
141+
for (var i = 0; i < parameters.length; i++) {
142+
var parameter = parameters[i];
144143
if (i < positional) {
145-
if (names.contains(argument.name)) return false;
146-
} else if (names.contains(argument.name)) {
144+
if (names.contains(parameter.name)) return false;
145+
} else if (names.contains(parameter.name)) {
147146
namedUsed++;
148-
} else if (argument.defaultValue == null) {
147+
} else if (parameter.defaultValue == null) {
149148
return false;
150149
}
151150
}
152151

153-
if (restArgument != null) return true;
154-
if (positional > arguments.length) return false;
152+
if (restParameter != null) return true;
153+
if (positional > parameters.length) return false;
155154
if (namedUsed < names.length) return false;
156155
return true;
157156
}
158157

159158
String toString() => [
160-
for (var arg in arguments) '\$$arg',
161-
if (restArgument != null) '\$$restArgument...'
159+
for (var arg in parameters) '\$$arg',
160+
if (restParameter != null) '\$$restParameter...'
162161
].join(', ');
163162
}

lib/src/ast/sass/statement/callable_declaration.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import 'package:source_span/source_span.dart';
66

7-
import '../argument_declaration.dart';
7+
import '../parameter_list.dart';
88
import '../statement.dart';
99
import 'parent.dart';
1010
import 'silent_comment.dart';
@@ -24,12 +24,12 @@ abstract base class CallableDeclaration
2424
/// The comment immediately preceding this declaration.
2525
final SilentComment? comment;
2626

27-
/// The declared arguments this callable accepts.
28-
final ArgumentDeclaration arguments;
27+
/// The declared parameters this callable accepts.
28+
final ParameterList parameters;
2929

3030
final FileSpan span;
3131

32-
CallableDeclaration(this.originalName, this.arguments,
32+
CallableDeclaration(this.originalName, this.parameters,
3333
Iterable<Statement> children, this.span,
3434
{this.comment})
3535
: name = originalName.replaceAll('_', '-'),

lib/src/ast/sass/statement/content_block.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ import 'package:source_span/source_span.dart';
66

77
import '../../../visitor/interface/statement.dart';
88
import '../statement.dart';
9-
import '../argument_declaration.dart';
9+
import '../parameter_list.dart';
1010
import 'callable_declaration.dart';
1111

1212
/// An anonymous block of code that's invoked for a [ContentRule].
1313
///
1414
/// {@category AST}
1515
final class ContentBlock extends CallableDeclaration {
16-
ContentBlock(ArgumentDeclaration arguments, Iterable<Statement> children,
17-
FileSpan span)
18-
: super("@content", arguments, children, span);
16+
ContentBlock(
17+
ParameterList parameters, Iterable<Statement> children, FileSpan span)
18+
: super("@content", parameters, children, span);
1919

2020
T accept<T>(StatementVisitor<T> visitor) => visitor.visitContentBlock(this);
2121

2222
String toString() =>
23-
(arguments.isEmpty ? "" : " using ($arguments)") +
23+
(parameters.isEmpty ? "" : " using ($parameters)") +
2424
" {${children.join(' ')}}";
2525
}

0 commit comments

Comments
 (0)