Skip to content

Commit 8270dc1

Browse files
authored
Support configuring modules through imports (#885)
Fixes #882 See sass/sass-spec#1497
1 parent 15be59b commit 8270dc1

File tree

7 files changed

+219
-147
lines changed

7 files changed

+219
-147
lines changed

CHANGELOG.md

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

3+
* Support configuring modules through `@import` rules.
4+
5+
## 1.23.8
6+
37
* **Potentially breaking bug fix:** Members loaded through a nested `@import`
48
are no longer ever accessible outside that nested context.
59

@@ -14,7 +18,7 @@
1418

1519
## 1.23.7
1620

17-
* No user-visible changes.
21+
* No user-visible changes
1822

1923
## 1.23.6
2024

lib/src/async_environment.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import 'ast/css.dart';
1313
import 'ast/node.dart';
1414
import 'ast/sass.dart';
1515
import 'callable.dart';
16+
import 'configuration.dart';
17+
import 'configured_value.dart';
1618
import 'exception.dart';
1719
import 'extend/extender.dart';
1820
import 'module.dart';
@@ -730,6 +732,23 @@ class AsyncEnvironment {
730732
}
731733
}
732734

735+
/// Creates an implicit configuration from the variables declared in this
736+
/// environment.
737+
Configuration toImplicitConfiguration() {
738+
var configuration = <String, ConfiguredValue>{};
739+
for (var i = 0; i < _variables.length; i++) {
740+
var values = _variables[i];
741+
var nodes =
742+
_variableNodes == null ? <String, AstNode>{} : _variableNodes[i];
743+
for (var name in values.keys) {
744+
// Implicit configurations are never invalid, making [configurationSpan]
745+
// unnecessary, so we pass null here to avoid having to compute it.
746+
configuration[name] = ConfiguredValue(values[name], null, nodes[name]);
747+
}
748+
}
749+
return Configuration(configuration, isImplicit: true);
750+
}
751+
733752
/// Returns a module that represents the top-level members defined in [this],
734753
/// that contains [css] as its CSS tree, which can be extended using
735754
/// [extender].

lib/src/configuration.dart

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2019 Google LLC. 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 'dart:collection';
6+
7+
import 'ast/sass.dart';
8+
import 'configured_value.dart';
9+
import 'util/limited_map_view.dart';
10+
import 'util/unprefixed_map_view.dart';
11+
12+
/// A set of variables meant to configure a module by overriding its
13+
/// `!default` declarations.
14+
class Configuration {
15+
/// A map from variable names (without `$`) to values.
16+
///
17+
/// This map may not be modified directly. To remove a value from this
18+
/// configuration, use the [remove] method.
19+
Map<String, ConfiguredValue> get values => UnmodifiableMapView(_values);
20+
final Map<String, ConfiguredValue> _values;
21+
22+
/// Whether or not this configuration is implicit.
23+
///
24+
/// Implicit configurations are created when a file containing a `@forward`
25+
/// rule is imported, while explicit configurations are created by the
26+
/// `with` clause of a `@use` rule.
27+
///
28+
/// Both types of configuration pass through `@forward` rules, but explicit
29+
/// configurations will cause an error if attempting to use them on a module
30+
/// that has already been loaded, while implicit configurations will be
31+
/// silently ignored in this case.
32+
final bool isImplicit;
33+
34+
Configuration(Map<String, ConfiguredValue> values, {this.isImplicit = false})
35+
: _values = values;
36+
37+
/// The empty configuration, which indicates that the module has not been
38+
/// configured.
39+
///
40+
/// Empty configurations are always considered implicit, since they are
41+
/// ignored if the module has already been loaded.
42+
const Configuration.empty()
43+
: _values = const {},
44+
isImplicit = true;
45+
46+
bool get isEmpty => values.isEmpty;
47+
48+
/// Removes a variable with [name] from this configuration, returning it.
49+
///
50+
/// If no such variable exists in this configuration, returns null.
51+
ConfiguredValue remove(String name) => isEmpty ? null : _values.remove(name);
52+
53+
/// Creates a new configuration from this one based on a `@forward` rule.
54+
Configuration throughForward(ForwardRule forward) {
55+
if (isEmpty) return const Configuration.empty();
56+
var newValues = _values;
57+
58+
// Only allow variables that are visible through the `@forward` to be
59+
// configured. These views support [Map.remove] so we can mark when a
60+
// configuration variable is used by removing it even when the underlying
61+
// map is wrapped.
62+
if (forward.prefix != null) {
63+
newValues = UnprefixedMapView(newValues, forward.prefix);
64+
}
65+
if (forward.shownVariables != null) {
66+
newValues = LimitedMapView.whitelist(newValues, forward.shownVariables);
67+
} else if (forward.hiddenVariables?.isNotEmpty ?? false) {
68+
newValues = LimitedMapView.blacklist(newValues, forward.hiddenVariables);
69+
}
70+
return Configuration(newValues, isImplicit: isImplicit);
71+
}
72+
73+
/// Creates a copy of this configuration.
74+
Configuration clone() => isEmpty
75+
? const Configuration.empty()
76+
: Configuration({...values}, isImplicit: isImplicit);
77+
}

lib/src/configured_value.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2019 Google LLC. 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 'ast/node.dart';
8+
import 'value.dart';
9+
10+
/// A variable value that's been configured for a [Configuration].
11+
class ConfiguredValue {
12+
/// The value of the variable.
13+
final Value value;
14+
15+
/// The span where the variable's configuration was written.
16+
final FileSpan configurationSpan;
17+
18+
/// The [AstNode] where the variable's value originated.
19+
///
20+
/// This is used to generate source maps and can be `null` if source map
21+
/// generation is disabled.
22+
final AstNode assignmentNode;
23+
24+
ConfiguredValue(this.value, this.configurationSpan, [this.assignmentNode]);
25+
}

lib/src/environment.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_environment.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: 0459cbe5c439f3d45f24b739ed1f36b517d338b8
8+
// Checksum: 7da67a8956ec74db270764e941b674dcc315a488
99
//
1010
// ignore_for_file: unused_import
1111

@@ -19,6 +19,8 @@ import 'ast/css.dart';
1919
import 'ast/node.dart';
2020
import 'ast/sass.dart';
2121
import 'callable.dart';
22+
import 'configuration.dart';
23+
import 'configured_value.dart';
2224
import 'exception.dart';
2325
import 'extend/extender.dart';
2426
import 'module.dart';
@@ -734,6 +736,23 @@ class Environment {
734736
}
735737
}
736738

739+
/// Creates an implicit configuration from the variables declared in this
740+
/// environment.
741+
Configuration toImplicitConfiguration() {
742+
var configuration = <String, ConfiguredValue>{};
743+
for (var i = 0; i < _variables.length; i++) {
744+
var values = _variables[i];
745+
var nodes =
746+
_variableNodes == null ? <String, AstNode>{} : _variableNodes[i];
747+
for (var name in values.keys) {
748+
// Implicit configurations are never invalid, making [configurationSpan]
749+
// unnecessary, so we pass null here to avoid having to compute it.
750+
configuration[name] = ConfiguredValue(values[name], null, nodes[name]);
751+
}
752+
}
753+
return Configuration(configuration, isImplicit: true);
754+
}
755+
737756
/// Returns a module that represents the top-level members defined in [this],
738757
/// that contains [css] as its CSS tree, which can be extended using
739758
/// [extender].

0 commit comments

Comments
 (0)