Skip to content

Commit b54643d

Browse files
authored
Merge pull request #895 from sass/forward-with
Support @forward ... with
2 parents 37534aa + 46be33e commit b54643d

File tree

11 files changed

+325
-108
lines changed

11 files changed

+325
-108
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## 1.24.0
22

3+
* Add an optional `with` clause to the `@forward` rule. This works like the
4+
`@use` rule's `with` clause, except that `@forward ... with` can declare
5+
variables as `!default` to allow downstream modules to reconfigure their
6+
values.
7+
38
* Support configuring modules through `@import` rules.
49

510
## 1.23.8

lib/src/ast/sass.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export 'sass/argument_declaration.dart';
77
export 'sass/argument_invocation.dart';
88
export 'sass/at_root_query.dart';
99
export 'sass/callable_invocation.dart';
10+
export 'sass/configured_variable.dart';
1011
export 'sass/expression.dart';
1112
export 'sass/expression/binary_operation.dart';
1213
export 'sass/expression/boolean.dart';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2019 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'package:source_span/source_span.dart';
6+
7+
import 'expression.dart';
8+
import 'node.dart';
9+
10+
/// A variable configured by a `with` clause in a `@use` or `@forward` rule.
11+
class ConfiguredVariable implements SassNode {
12+
/// The name of the variable being configured.
13+
final String name;
14+
15+
/// The variable's value.
16+
final Expression expression;
17+
18+
/// Whether the variable can be further configured by outer modules.
19+
///
20+
/// This is always `false` for `@use` rules.
21+
final bool isGuarded;
22+
23+
final FileSpan span;
24+
25+
ConfiguredVariable(this.name, this.expression, this.span,
26+
{bool guarded = false})
27+
: isGuarded = guarded;
28+
29+
String toString() => "\$$name: $expression${isGuarded ? ' !default' : ''}";
30+
}

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

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

88
import '../../../visitor/interface/statement.dart';
9+
import '../configured_variable.dart';
910
import '../expression/string.dart';
1011
import '../statement.dart';
1112

@@ -64,36 +65,46 @@ class ForwardRule implements Statement {
6465
/// module, or `null` if member names are used as-is.
6566
final String prefix;
6667

68+
/// A list of variable assignments used to configure the loaded modules.
69+
final List<ConfiguredVariable> configuration;
70+
6771
final FileSpan span;
6872

6973
/// Creates a `@forward` rule that allows all members to be accessed.
70-
ForwardRule(this.url, this.span, {this.prefix})
74+
ForwardRule(this.url, this.span,
75+
{this.prefix, Iterable<ConfiguredVariable> configuration})
7176
: shownMixinsAndFunctions = null,
7277
shownVariables = null,
7378
hiddenMixinsAndFunctions = null,
74-
hiddenVariables = null;
79+
hiddenVariables = null,
80+
configuration =
81+
configuration == null ? const [] : List.unmodifiable(configuration);
7582

7683
/// Creates a `@forward` rule that allows only members included in
7784
/// [shownMixinsAndFunctions] and [shownVariables] to be accessed.
7885
ForwardRule.show(this.url, Iterable<String> shownMixinsAndFunctions,
7986
Iterable<String> shownVariables, this.span,
80-
{this.prefix})
87+
{this.prefix, Iterable<ConfiguredVariable> configuration})
8188
: shownMixinsAndFunctions =
8289
UnmodifiableSetView(Set.of(shownMixinsAndFunctions)),
8390
shownVariables = UnmodifiableSetView(Set.of(shownVariables)),
8491
hiddenMixinsAndFunctions = null,
85-
hiddenVariables = null;
92+
hiddenVariables = null,
93+
configuration =
94+
configuration == null ? const [] : List.unmodifiable(configuration);
8695

8796
/// Creates a `@forward` rule that allows only members not included in
8897
/// [hiddenMixinsAndFunctions] and [hiddenVariables] to be accessed.
8998
ForwardRule.hide(this.url, Iterable<String> hiddenMixinsAndFunctions,
9099
Iterable<String> hiddenVariables, this.span,
91-
{this.prefix})
100+
{this.prefix, Iterable<ConfiguredVariable> configuration})
92101
: shownMixinsAndFunctions = null,
93102
shownVariables = null,
94103
hiddenMixinsAndFunctions =
95104
UnmodifiableSetView(Set.of(hiddenMixinsAndFunctions)),
96-
hiddenVariables = UnmodifiableSetView(Set.of(hiddenVariables));
105+
hiddenVariables = UnmodifiableSetView(Set.of(hiddenVariables)),
106+
configuration =
107+
configuration == null ? const [] : List.unmodifiable(configuration);
97108

98109
T accept<T>(StatementVisitor<T> visitor) => visitor.visitForwardRule(this);
99110

@@ -112,6 +123,11 @@ class ForwardRule implements Statement {
112123
}
113124

114125
if (prefix != null) buffer.write(" as $prefix*");
126+
127+
if (configuration.isNotEmpty) {
128+
buffer.write(" with (${configuration.join(", ")})");
129+
}
130+
115131
buffer.write(";");
116132
return buffer.toString();
117133
}

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
// https://opensource.org/licenses/MIT.
44

55
import 'package:source_span/source_span.dart';
6-
import 'package:tuple/tuple.dart';
76

87
import '../../../logger.dart';
98
import '../../../parse/scss.dart';
109
import '../../../visitor/interface/statement.dart';
11-
import '../expression.dart';
10+
import '../configured_variable.dart';
1211
import '../expression/string.dart';
1312
import '../statement.dart';
1413

@@ -23,15 +22,23 @@ class UseRule implements Statement {
2322
/// can be accessed without a namespace.
2423
final String namespace;
2524

26-
/// A map from variable names to their values and the spans for those
27-
/// variables, used to configure the loaded modules.
28-
final Map<String, Tuple2<Expression, FileSpan>> configuration;
25+
/// A list of variable assignments used to configure the loaded modules.
26+
final List<ConfiguredVariable> configuration;
2927

3028
final FileSpan span;
3129

3230
UseRule(this.url, this.namespace, this.span,
33-
{Map<String, Tuple2<Expression, FileSpan>> configuration})
34-
: configuration = Map.unmodifiable(configuration ?? const {});
31+
{Iterable<ConfiguredVariable> configuration})
32+
: configuration = configuration == null
33+
? const []
34+
: List.unmodifiable(configuration) {
35+
for (var variable in this.configuration) {
36+
if (variable.isGuarded) {
37+
throw ArgumentError.value(variable, "configured variable",
38+
"can't be guarded in a @use rule.");
39+
}
40+
}
41+
}
3542

3643
/// Parses a `@use` rule from [contents].
3744
///
@@ -54,11 +61,7 @@ class UseRule implements Statement {
5461
}
5562

5663
if (configuration.isNotEmpty) {
57-
buffer.write(" with (");
58-
buffer.write(configuration.entries
59-
.map((entry) => "\$${entry.key}: ${entry.value.item1}")
60-
.join(", "));
61-
buffer.write(")");
64+
buffer.write(" with (${configuration.join(", ")})");
6265
}
6366

6467
buffer.write(";");

lib/src/configuration.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,4 @@ class Configuration {
6969
}
7070
return Configuration(newValues, isImplicit: isImplicit);
7171
}
72-
73-
/// Creates a copy of this configuration.
74-
Configuration clone() => isEmpty
75-
? const Configuration.empty()
76-
: Configuration({...values}, isImplicit: isImplicit);
7772
}

lib/src/parse/stylesheet.dart

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,8 @@ abstract class StylesheetParser extends Parser {
962962
hiddenVariables = members.item2;
963963
}
964964

965+
var configuration = _configuration(allowGuarded: true);
966+
965967
expectStatementSeparator("@forward rule");
966968
var span = scanner.spanFrom(start);
967969
if (!_isUseAllowed) {
@@ -971,13 +973,14 @@ abstract class StylesheetParser extends Parser {
971973
if (shownMixinsAndFunctions != null) {
972974
return ForwardRule.show(
973975
url, shownMixinsAndFunctions, shownVariables, span,
974-
prefix: prefix);
976+
prefix: prefix, configuration: configuration);
975977
} else if (hiddenMixinsAndFunctions != null) {
976978
return ForwardRule.hide(
977979
url, hiddenMixinsAndFunctions, hiddenVariables, span,
978-
prefix: prefix);
980+
prefix: prefix, configuration: configuration);
979981
} else {
980-
return ForwardRule(url, span, prefix: prefix);
982+
return ForwardRule(url, span,
983+
prefix: prefix, configuration: configuration);
981984
}
982985
}
983986

@@ -1350,7 +1353,7 @@ relase. For details, see http://bit.ly/moz-document.
13501353

13511354
var namespace = _useNamespace(url, start);
13521355
whitespace();
1353-
var configuration = _useConfiguration();
1356+
var configuration = _configuration();
13541357

13551358
expectStatementSeparator("@use rule");
13561359

@@ -1381,14 +1384,18 @@ relase. For details, see http://bit.ly/moz-document.
13811384
}
13821385
}
13831386

1384-
/// Returns the map from variable names to expressions from a `@use` rule's
1385-
/// `with` clause.
1387+
/// Returns the list of configured variables from a `@use` or `@forward`
1388+
/// rule's `with` clause.
1389+
///
1390+
/// If `allowGuarded` is `true`, this will allow configured variable with the
1391+
/// `!default` flag.
13861392
///
13871393
/// Returns `null` if there is no `with` clause.
1388-
Map<String, Tuple2<Expression, FileSpan>> _useConfiguration() {
1394+
List<ConfiguredVariable> _configuration({bool allowGuarded = false}) {
13891395
if (!scanIdentifier("with")) return null;
13901396

1391-
var configuration = <String, Tuple2<Expression, FileSpan>>{};
1397+
var variableNames = <String>{};
1398+
var configuration = <ConfiguredVariable>[];
13921399
whitespace();
13931400
scanner.expectChar($lparen);
13941401

@@ -1401,12 +1408,25 @@ relase. For details, see http://bit.ly/moz-document.
14011408
scanner.expectChar($colon);
14021409
whitespace();
14031410
var expression = _expressionUntilComma();
1404-
var span = scanner.spanFrom(variableStart);
14051411

1406-
if (configuration.containsKey(name)) {
1412+
var guarded = false;
1413+
var flagStart = scanner.state;
1414+
if (allowGuarded && scanner.scanChar($exclamation)) {
1415+
var flag = identifier();
1416+
if (flag == 'default') {
1417+
guarded = true;
1418+
} else {
1419+
error("Invalid flag name.", scanner.spanFrom(flagStart));
1420+
}
1421+
}
1422+
1423+
var span = scanner.spanFrom(variableStart);
1424+
if (variableNames.contains(name)) {
14071425
error("The same variable may only be configured once.", span);
14081426
}
1409-
configuration[name] = Tuple2(expression, span);
1427+
variableNames.add(name);
1428+
configuration
1429+
.add(ConfiguredVariable(name, expression, span, guarded: guarded));
14101430

14111431
if (!scanner.scanChar($comma)) break;
14121432
whitespace();

lib/src/util/merged_map_view.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import '../utils.dart';
1414
/// key.
1515
///
1616
/// Unlike `CombinedMapView` from the `collection` package, this provides `O(1)`
17-
/// index and `length` operations. It does so by imposing the additional
18-
/// constraint that the underlying maps' sets of keys remain unchanged.
17+
/// index and `length` operations and provides some degree of mutability. It
18+
/// does so by imposing the additional constraint that the underlying maps' sets
19+
/// of keys remain unchanged.
1920
class MergedMapView<K, V> extends MapBase<K, V> {
2021
// A map from keys to the maps in which those keys first appear.
2122
final _mapsByKey = <K, Map<K, V>>{};

0 commit comments

Comments
 (0)