-
Notifications
You must be signed in to change notification settings - Fork 347
/
configuration.dart
139 lines (120 loc) · 5.67 KB
/
configuration.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright 2019 Google LLC. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:collection';
import 'ast/node.dart';
import 'ast/sass.dart';
import 'configured_value.dart';
import 'util/limited_map_view.dart';
import 'util/unprefixed_map_view.dart';
/// A set of variables meant to configure a module by overriding its
/// `!default` declarations.
///
/// A configuration may be either *implicit*, meaning that it's either empty or
/// created by importing a file containing a `@forward` rule; or *explicit*,
/// meaning that it's created by passing a `with` clause to a `@use` rule.
/// Explicit configurations have spans associated with them and are represented
/// by the [ExplicitConfiguration] subclass.
class Configuration {
/// A map from variable names (without `$`) to values.
///
/// This map may not be modified directly. To remove a value from this
/// configuration, use the [remove] method.
Map<String, ConfiguredValue> get values => UnmodifiableMapView(_values);
final Map<String, ConfiguredValue> _values;
/// Creates an implicit configuration with the given [values].
Configuration.implicit(this._values) : __originalConfiguration = null;
/// The backing value for [_originalConfiguration].
///
/// This is null if [_originalConfiguration] refers to itself since `this`
/// can't be assigned to a final field.
final Configuration? __originalConfiguration;
/// The configuration from which this was modified with `@forward ... with`.
///
/// This reference serves as an opaque ID.
Configuration get _originalConfiguration => __originalConfiguration ?? this;
/// Returns whether `this` and [that] [Configuration]s have the same
/// [_originalConfiguration].
///
/// An implicit configuration will always return `false` because it was not
/// created through another configuration.
///
/// [ExplicitConfiguration]s will and configurations created [throughForward]
/// will be considered to have the same original config if they were created
/// as a copy from the same base configuration.
bool sameOriginal(Configuration that) =>
_originalConfiguration == that._originalConfiguration;
/// The empty configuration, which indicates that the module has not been
/// configured.
///
/// Empty configurations are always considered implicit, since they are
/// ignored if the module has already been loaded.
const Configuration.empty()
: _values = const {},
__originalConfiguration = null;
bool get isEmpty => values.isEmpty;
/// Removes a variable with [name] from this configuration, returning it.
///
/// If no such variable exists in this configuration, returns null.
ConfiguredValue? remove(String name) => isEmpty ? null : _values.remove(name);
/// Creates a new configuration from this one based on a `@forward` rule.
Configuration throughForward(ForwardRule forward) {
if (isEmpty) return const Configuration.empty();
var newValues = _values;
// Only allow variables that are visible through the `@forward` to be
// configured. These views support [Map.remove] so we can mark when a
// configuration variable is used by removing it even when the underlying
// map is wrapped.
var prefix = forward.prefix;
if (prefix != null) newValues = UnprefixedMapView(newValues, prefix);
var shownVariables = forward.shownVariables;
var hiddenVariables = forward.hiddenVariables;
if (shownVariables != null) {
newValues = LimitedMapView.safelist(newValues, shownVariables);
} else if (hiddenVariables != null && hiddenVariables.isNotEmpty) {
newValues = LimitedMapView.blocklist(newValues, hiddenVariables);
}
return _withValues(newValues);
}
/// Returns a copy of `this` [Configuration] with the given [values] map.
///
/// The copy will have the same [_originalConfiguration] as `this` config.
Configuration _withValues(Map<String, ConfiguredValue> values) =>
Configuration._(values, _originalConfiguration);
/// Creates a [Configuration] with the given [_values] map and an
/// [_originalConfiguration] reference.
Configuration._(this._values, this.__originalConfiguration);
@override
String toString() =>
"(" +
values.entries
.map((entry) => "\$${entry.key}: ${entry.value}")
.join(", ") +
")";
}
/// A [Configuration] that was created with an explicit `with` clause of a
/// `@use` rule.
///
/// Both types of configuration pass through `@forward` rules, but explicit
/// configurations will cause an error if attempting to use them on a module
/// that has already been loaded, while implicit configurations will be
/// silently ignored in this case.
class ExplicitConfiguration extends Configuration {
/// The node whose span indicates where the configuration was declared.
final AstNode nodeWithSpan;
/// Creates a base [ExplicitConfiguration] with a [values] map and a
/// [nodeWithSpan].
ExplicitConfiguration(Map<String, ConfiguredValue> values, this.nodeWithSpan)
: super.implicit(values);
/// Creates an [ExplicitConfiguration] with a [values] map, a [nodeWithSpan]
/// and if this is a copy a reference to the [_originalConfiguration].
ExplicitConfiguration._(Map<String, ConfiguredValue> values,
this.nodeWithSpan, Configuration? originalConfiguration)
: super._(values, originalConfiguration);
/// Returns a copy of `this` with the given [values] map.
///
/// The copy will have the same [_originalConfiguration] as `this` config.
@override
Configuration _withValues(Map<String, ConfiguredValue> values) =>
ExplicitConfiguration._(values, nodeWithSpan, _originalConfiguration);
}