mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-22 13:51:31 +01:00
Support configuring modules through imports (#885)
Fixes #882 See sass/sass-spec#1497
This commit is contained in:
parent
15be59be31
commit
8270dc1664
@ -1,5 +1,9 @@
|
||||
## 1.24.0
|
||||
|
||||
* Support configuring modules through `@import` rules.
|
||||
|
||||
## 1.23.8
|
||||
|
||||
* **Potentially breaking bug fix:** Members loaded through a nested `@import`
|
||||
are no longer ever accessible outside that nested context.
|
||||
|
||||
@ -14,7 +18,7 @@
|
||||
|
||||
## 1.23.7
|
||||
|
||||
* No user-visible changes.
|
||||
* No user-visible changes
|
||||
|
||||
## 1.23.6
|
||||
|
||||
|
@ -13,6 +13,8 @@ import 'ast/css.dart';
|
||||
import 'ast/node.dart';
|
||||
import 'ast/sass.dart';
|
||||
import 'callable.dart';
|
||||
import 'configuration.dart';
|
||||
import 'configured_value.dart';
|
||||
import 'exception.dart';
|
||||
import 'extend/extender.dart';
|
||||
import 'module.dart';
|
||||
@ -730,6 +732,23 @@ class AsyncEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an implicit configuration from the variables declared in this
|
||||
/// environment.
|
||||
Configuration toImplicitConfiguration() {
|
||||
var configuration = <String, ConfiguredValue>{};
|
||||
for (var i = 0; i < _variables.length; i++) {
|
||||
var values = _variables[i];
|
||||
var nodes =
|
||||
_variableNodes == null ? <String, AstNode>{} : _variableNodes[i];
|
||||
for (var name in values.keys) {
|
||||
// Implicit configurations are never invalid, making [configurationSpan]
|
||||
// unnecessary, so we pass null here to avoid having to compute it.
|
||||
configuration[name] = ConfiguredValue(values[name], null, nodes[name]);
|
||||
}
|
||||
}
|
||||
return Configuration(configuration, isImplicit: true);
|
||||
}
|
||||
|
||||
/// Returns a module that represents the top-level members defined in [this],
|
||||
/// that contains [css] as its CSS tree, which can be extended using
|
||||
/// [extender].
|
||||
|
77
lib/src/configuration.dart
Normal file
77
lib/src/configuration.dart
Normal file
@ -0,0 +1,77 @@
|
||||
// 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/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.
|
||||
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;
|
||||
|
||||
/// Whether or not this configuration is implicit.
|
||||
///
|
||||
/// Implicit configurations are created when a file containing a `@forward`
|
||||
/// rule is imported, while explicit configurations are created by the
|
||||
/// `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.
|
||||
final bool isImplicit;
|
||||
|
||||
Configuration(Map<String, ConfiguredValue> values, {this.isImplicit = false})
|
||||
: _values = values;
|
||||
|
||||
/// 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 {},
|
||||
isImplicit = true;
|
||||
|
||||
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.
|
||||
if (forward.prefix != null) {
|
||||
newValues = UnprefixedMapView(newValues, forward.prefix);
|
||||
}
|
||||
if (forward.shownVariables != null) {
|
||||
newValues = LimitedMapView.whitelist(newValues, forward.shownVariables);
|
||||
} else if (forward.hiddenVariables?.isNotEmpty ?? false) {
|
||||
newValues = LimitedMapView.blacklist(newValues, forward.hiddenVariables);
|
||||
}
|
||||
return Configuration(newValues, isImplicit: isImplicit);
|
||||
}
|
||||
|
||||
/// Creates a copy of this configuration.
|
||||
Configuration clone() => isEmpty
|
||||
? const Configuration.empty()
|
||||
: Configuration({...values}, isImplicit: isImplicit);
|
||||
}
|
25
lib/src/configured_value.dart
Normal file
25
lib/src/configured_value.dart
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 'package:source_span/source_span.dart';
|
||||
|
||||
import 'ast/node.dart';
|
||||
import 'value.dart';
|
||||
|
||||
/// A variable value that's been configured for a [Configuration].
|
||||
class ConfiguredValue {
|
||||
/// The value of the variable.
|
||||
final Value value;
|
||||
|
||||
/// The span where the variable's configuration was written.
|
||||
final FileSpan configurationSpan;
|
||||
|
||||
/// The [AstNode] where the variable's value originated.
|
||||
///
|
||||
/// This is used to generate source maps and can be `null` if source map
|
||||
/// generation is disabled.
|
||||
final AstNode assignmentNode;
|
||||
|
||||
ConfiguredValue(this.value, this.configurationSpan, [this.assignmentNode]);
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_environment.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 0459cbe5c439f3d45f24b739ed1f36b517d338b8
|
||||
// Checksum: 7da67a8956ec74db270764e941b674dcc315a488
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -19,6 +19,8 @@ import 'ast/css.dart';
|
||||
import 'ast/node.dart';
|
||||
import 'ast/sass.dart';
|
||||
import 'callable.dart';
|
||||
import 'configuration.dart';
|
||||
import 'configured_value.dart';
|
||||
import 'exception.dart';
|
||||
import 'extend/extender.dart';
|
||||
import 'module.dart';
|
||||
@ -734,6 +736,23 @@ class Environment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an implicit configuration from the variables declared in this
|
||||
/// environment.
|
||||
Configuration toImplicitConfiguration() {
|
||||
var configuration = <String, ConfiguredValue>{};
|
||||
for (var i = 0; i < _variables.length; i++) {
|
||||
var values = _variables[i];
|
||||
var nodes =
|
||||
_variableNodes == null ? <String, AstNode>{} : _variableNodes[i];
|
||||
for (var name in values.keys) {
|
||||
// Implicit configurations are never invalid, making [configurationSpan]
|
||||
// unnecessary, so we pass null here to avoid having to compute it.
|
||||
configuration[name] = ConfiguredValue(values[name], null, nodes[name]);
|
||||
}
|
||||
}
|
||||
return Configuration(configuration, isImplicit: true);
|
||||
}
|
||||
|
||||
/// Returns a module that represents the top-level members defined in [this],
|
||||
/// that contains [css] as its CSS tree, which can be extended using
|
||||
/// [extender].
|
||||
|
@ -22,6 +22,8 @@ import '../async_environment.dart';
|
||||
import '../async_import_cache.dart';
|
||||
import '../callable.dart';
|
||||
import '../color_names.dart';
|
||||
import '../configuration.dart';
|
||||
import '../configured_value.dart';
|
||||
import '../exception.dart';
|
||||
import '../extend/extender.dart';
|
||||
import '../extend/extension.dart';
|
||||
@ -37,8 +39,6 @@ import '../module/built_in.dart';
|
||||
import '../parse/keyframe_selector.dart';
|
||||
import '../syntax.dart';
|
||||
import '../util/fixed_length_list_builder.dart';
|
||||
import '../util/limited_map_view.dart';
|
||||
import '../util/unprefixed_map_view.dart';
|
||||
import '../utils.dart';
|
||||
import '../value.dart';
|
||||
import '../warn.dart';
|
||||
@ -251,13 +251,10 @@ class _EvaluateVisitor
|
||||
/// module.
|
||||
Extender _extender;
|
||||
|
||||
/// A map from variable names to the values that override their `!default`
|
||||
/// definitions in this module.
|
||||
/// The configuration for the current module.
|
||||
///
|
||||
/// If this is empty, that indicates that the current module is not confiured.
|
||||
/// Note that it may be unmodifiable when empty, in which case [Map.remove]
|
||||
/// must not be called.
|
||||
var _configuration = const <String, _ConfiguredValue>{};
|
||||
var _configuration = const Configuration.empty();
|
||||
|
||||
/// Creates a new visitor.
|
||||
///
|
||||
@ -413,19 +410,20 @@ class _EvaluateVisitor
|
||||
var url = Uri.parse(arguments[0].assertString("module").text);
|
||||
var withMap = arguments[1].realNull?.assertMap("with")?.contents;
|
||||
|
||||
var configuration = const <String, _ConfiguredValue>{};
|
||||
var configuration = const Configuration.empty();
|
||||
if (withMap != null) {
|
||||
configuration = {};
|
||||
var values = <String, ConfiguredValue>{};
|
||||
var span = _callableNode.span;
|
||||
withMap.forEach((variable, value) {
|
||||
var name =
|
||||
variable.assertString("with key").text.replaceAll("_", "-");
|
||||
if (configuration.containsKey(name)) {
|
||||
if (values.containsKey(name)) {
|
||||
throw "The variable \$$name was configured twice.";
|
||||
}
|
||||
|
||||
configuration[name] = _ConfiguredValue(value, span);
|
||||
values[name] = ConfiguredValue(value, span);
|
||||
});
|
||||
configuration = Configuration(values);
|
||||
}
|
||||
|
||||
await _loadModule(url, "load-css()", _callableNode,
|
||||
@ -523,11 +521,11 @@ class _EvaluateVisitor
|
||||
Future<void> _loadModule(Uri url, String stackFrame, AstNode nodeForSpan,
|
||||
void callback(Module module),
|
||||
{Uri baseUrl,
|
||||
Map<String, _ConfiguredValue> configuration,
|
||||
Configuration configuration,
|
||||
bool namesInErrors = false}) async {
|
||||
var builtInModule = _builtInModules[url];
|
||||
if (builtInModule != null) {
|
||||
if (configuration != null && configuration.isNotEmpty) {
|
||||
if (configuration != null && !configuration.isImplicit) {
|
||||
throw _exception(
|
||||
namesInErrors
|
||||
? "Built-in module $url can't be configured."
|
||||
@ -576,22 +574,20 @@ class _EvaluateVisitor
|
||||
|
||||
/// Executes [stylesheet], loaded by [importer], to produce a module.
|
||||
///
|
||||
/// The [configuration] overrides values for `!default` variables defined in
|
||||
/// the module or modules it forwards and/or imports. If it's not passed, the
|
||||
/// current configuration is used instead. Throws a [SassRuntimeException] if
|
||||
/// a configured variable is not declared with `!default`.
|
||||
/// If [configuration] is not passed, the current configuration is used
|
||||
/// instead. Throws a [SassRuntimeException] if a configured variable is not
|
||||
/// declared with `!default`.
|
||||
///
|
||||
/// If [namesInErrors] is `true`, this includes the names of modules or
|
||||
/// configured variables in errors relating to them. This should only be
|
||||
/// `true` if the names won't be obvious from the source span.
|
||||
Future<Module> _execute(AsyncImporter importer, Stylesheet stylesheet,
|
||||
{Map<String, _ConfiguredValue> configuration,
|
||||
bool namesInErrors = false}) async {
|
||||
{Configuration configuration, bool namesInErrors = false}) async {
|
||||
var url = stylesheet.span.sourceUrl;
|
||||
|
||||
var alreadyLoaded = _modules[url];
|
||||
if (alreadyLoaded != null) {
|
||||
if ((configuration ?? _configuration).isNotEmpty) {
|
||||
if (!(configuration ?? _configuration).isImplicit) {
|
||||
throw _exception(namesInErrors
|
||||
? "${p.prettyUri(url)} was already loaded, so it can't be "
|
||||
"configured using \"with\"."
|
||||
@ -634,10 +630,7 @@ class _EvaluateVisitor
|
||||
_atRootExcludingStyleRule = false;
|
||||
_inKeyframes = false;
|
||||
|
||||
if (configuration != null) {
|
||||
_configuration =
|
||||
configuration.isEmpty ? const {} : Map.of(configuration);
|
||||
}
|
||||
if (configuration != null) _configuration = configuration.clone();
|
||||
|
||||
await visitStylesheet(stylesheet);
|
||||
css = _outOfOrderImports == null
|
||||
@ -658,14 +651,16 @@ class _EvaluateVisitor
|
||||
_atRootExcludingStyleRule = oldAtRootExcludingStyleRule;
|
||||
_inKeyframes = oldInKeyframes;
|
||||
|
||||
if (configuration != null && _configuration.isNotEmpty) {
|
||||
if (configuration != null &&
|
||||
!_configuration.isEmpty &&
|
||||
!_configuration.isImplicit) {
|
||||
throw _exception(
|
||||
namesInErrors
|
||||
? "\$${_configuration.keys.first} was not declared with "
|
||||
? "\$${_configuration.values.keys.first} was not declared with "
|
||||
"!default in the @used module."
|
||||
: "This variable was not declared with !default in the @used "
|
||||
"module.",
|
||||
_configuration.values.first.configurationSpan);
|
||||
_configuration.values.values.first.configurationSpan);
|
||||
}
|
||||
_configuration = oldConfiguration;
|
||||
});
|
||||
@ -1203,25 +1198,8 @@ class _EvaluateVisitor
|
||||
}
|
||||
|
||||
Future<Value> visitForwardRule(ForwardRule node) async {
|
||||
// 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 oldConfiguration = _configuration;
|
||||
if (_configuration.isNotEmpty) {
|
||||
if (node.prefix != null) {
|
||||
_configuration = UnprefixedMapView(_configuration, node.prefix);
|
||||
}
|
||||
|
||||
if (node.shownVariables != null) {
|
||||
_configuration =
|
||||
LimitedMapView.whitelist(_configuration, node.shownVariables);
|
||||
} else if (node.hiddenVariables != null &&
|
||||
node.hiddenVariables.isNotEmpty) {
|
||||
_configuration =
|
||||
LimitedMapView.blacklist(_configuration, node.hiddenVariables);
|
||||
}
|
||||
}
|
||||
_configuration = _configuration.throughForward(node);
|
||||
|
||||
await _loadModule(node.url, "@forward", node, (module) {
|
||||
_environment.forwardModule(module, node);
|
||||
@ -1303,6 +1281,7 @@ class _EvaluateVisitor
|
||||
var oldParent = _parent;
|
||||
var oldEndOfImports = _endOfImports;
|
||||
var oldOutOfOrderImports = _outOfOrderImports;
|
||||
var oldConfiguration = _configuration;
|
||||
_importer = importer;
|
||||
_stylesheet = stylesheet;
|
||||
_root = ModifiableCssStylesheet(stylesheet.span);
|
||||
@ -1310,6 +1289,12 @@ class _EvaluateVisitor
|
||||
_endOfImports = 0;
|
||||
_outOfOrderImports = null;
|
||||
|
||||
// This configuration is only used if it passes through a `@forward`
|
||||
// rule, so we avoid creating unnecessary ones for performance reasons.
|
||||
if (stylesheet.forwards.isNotEmpty) {
|
||||
_configuration = environment.toImplicitConfiguration();
|
||||
}
|
||||
|
||||
await visitStylesheet(stylesheet);
|
||||
children = _addOutOfOrderImports();
|
||||
|
||||
@ -1319,6 +1304,7 @@ class _EvaluateVisitor
|
||||
_parent = oldParent;
|
||||
_endOfImports = oldEndOfImports;
|
||||
_outOfOrderImports = oldOutOfOrderImports;
|
||||
_configuration = oldConfiguration;
|
||||
});
|
||||
|
||||
// Create a dummy module with empty CSS and no extensions to make forwarded
|
||||
@ -1726,12 +1712,7 @@ class _EvaluateVisitor
|
||||
Future<Value> visitVariableDeclaration(VariableDeclaration node) async {
|
||||
if (node.isGuarded) {
|
||||
if (node.namespace == null && _environment.atRoot) {
|
||||
// Explicitly check whether [_configuration] is empty because if it is,
|
||||
// it may be a constant map which doesn't support `remove()`.
|
||||
//
|
||||
// See also dart-lang/sdk#38540.
|
||||
var override =
|
||||
_configuration.isEmpty ? null : _configuration.remove(node.name);
|
||||
var override = _configuration.remove(node.name);
|
||||
if (override != null) {
|
||||
_addExceptionSpan(node, () {
|
||||
_environment.setVariable(
|
||||
@ -1777,14 +1758,14 @@ class _EvaluateVisitor
|
||||
_environment.addModule(module, namespace: node.namespace);
|
||||
},
|
||||
configuration: node.configuration.isEmpty
|
||||
? const {}
|
||||
: {
|
||||
? const Configuration.empty()
|
||||
: Configuration({
|
||||
for (var entry in node.configuration.entries)
|
||||
entry.key: _ConfiguredValue(
|
||||
entry.key: ConfiguredValue(
|
||||
(await entry.value.item1.accept(this)).withoutSlash(),
|
||||
entry.value.item2,
|
||||
_expressionNode(entry.value.item1))
|
||||
});
|
||||
}));
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -2982,19 +2963,3 @@ class _ArgumentResults {
|
||||
_ArgumentResults(this.positional, this.named, this.separator,
|
||||
{this.positionalNodes, this.namedNodes});
|
||||
}
|
||||
|
||||
/// A variable value that's been configured using `@use ... with`.
|
||||
class _ConfiguredValue {
|
||||
/// The value of the variable.
|
||||
final Value value;
|
||||
|
||||
/// The span where the variable's configuration was written.
|
||||
final FileSpan configurationSpan;
|
||||
|
||||
/// The [AstNode] where the variable's value originated.
|
||||
///
|
||||
/// This is used to generate source maps.
|
||||
final AstNode assignmentNode;
|
||||
|
||||
_ConfiguredValue(this.value, this.configurationSpan, [this.assignmentNode]);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_evaluate.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 8b0be6a2009429b4a3a915f5ad5850e7891dd94d
|
||||
// Checksum: 63ce60ba47ac04b49e0a3edbe9038fb13b037e64
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -31,6 +31,8 @@ import '../environment.dart';
|
||||
import '../import_cache.dart';
|
||||
import '../callable.dart';
|
||||
import '../color_names.dart';
|
||||
import '../configuration.dart';
|
||||
import '../configured_value.dart';
|
||||
import '../exception.dart';
|
||||
import '../extend/extender.dart';
|
||||
import '../extend/extension.dart';
|
||||
@ -46,8 +48,6 @@ import '../module/built_in.dart';
|
||||
import '../parse/keyframe_selector.dart';
|
||||
import '../syntax.dart';
|
||||
import '../util/fixed_length_list_builder.dart';
|
||||
import '../util/limited_map_view.dart';
|
||||
import '../util/unprefixed_map_view.dart';
|
||||
import '../utils.dart';
|
||||
import '../value.dart';
|
||||
import '../warn.dart';
|
||||
@ -259,13 +259,10 @@ class _EvaluateVisitor
|
||||
/// module.
|
||||
Extender _extender;
|
||||
|
||||
/// A map from variable names to the values that override their `!default`
|
||||
/// definitions in this module.
|
||||
/// The configuration for the current module.
|
||||
///
|
||||
/// If this is empty, that indicates that the current module is not confiured.
|
||||
/// Note that it may be unmodifiable when empty, in which case [Map.remove]
|
||||
/// must not be called.
|
||||
var _configuration = const <String, _ConfiguredValue>{};
|
||||
var _configuration = const Configuration.empty();
|
||||
|
||||
/// Creates a new visitor.
|
||||
///
|
||||
@ -419,19 +416,20 @@ class _EvaluateVisitor
|
||||
var url = Uri.parse(arguments[0].assertString("module").text);
|
||||
var withMap = arguments[1].realNull?.assertMap("with")?.contents;
|
||||
|
||||
var configuration = const <String, _ConfiguredValue>{};
|
||||
var configuration = const Configuration.empty();
|
||||
if (withMap != null) {
|
||||
configuration = {};
|
||||
var values = <String, ConfiguredValue>{};
|
||||
var span = _callableNode.span;
|
||||
withMap.forEach((variable, value) {
|
||||
var name =
|
||||
variable.assertString("with key").text.replaceAll("_", "-");
|
||||
if (configuration.containsKey(name)) {
|
||||
if (values.containsKey(name)) {
|
||||
throw "The variable \$$name was configured twice.";
|
||||
}
|
||||
|
||||
configuration[name] = _ConfiguredValue(value, span);
|
||||
values[name] = ConfiguredValue(value, span);
|
||||
});
|
||||
configuration = Configuration(values);
|
||||
}
|
||||
|
||||
_loadModule(url, "load-css()", _callableNode,
|
||||
@ -528,12 +526,10 @@ class _EvaluateVisitor
|
||||
/// the stack frame for the duration of the [callback].
|
||||
void _loadModule(Uri url, String stackFrame, AstNode nodeForSpan,
|
||||
void callback(Module<Callable> module),
|
||||
{Uri baseUrl,
|
||||
Map<String, _ConfiguredValue> configuration,
|
||||
bool namesInErrors = false}) {
|
||||
{Uri baseUrl, Configuration configuration, bool namesInErrors = false}) {
|
||||
var builtInModule = _builtInModules[url];
|
||||
if (builtInModule != null) {
|
||||
if (configuration != null && configuration.isNotEmpty) {
|
||||
if (configuration != null && !configuration.isImplicit) {
|
||||
throw _exception(
|
||||
namesInErrors
|
||||
? "Built-in module $url can't be configured."
|
||||
@ -582,22 +578,20 @@ class _EvaluateVisitor
|
||||
|
||||
/// Executes [stylesheet], loaded by [importer], to produce a module.
|
||||
///
|
||||
/// The [configuration] overrides values for `!default` variables defined in
|
||||
/// the module or modules it forwards and/or imports. If it's not passed, the
|
||||
/// current configuration is used instead. Throws a [SassRuntimeException] if
|
||||
/// a configured variable is not declared with `!default`.
|
||||
/// If [configuration] is not passed, the current configuration is used
|
||||
/// instead. Throws a [SassRuntimeException] if a configured variable is not
|
||||
/// declared with `!default`.
|
||||
///
|
||||
/// If [namesInErrors] is `true`, this includes the names of modules or
|
||||
/// configured variables in errors relating to them. This should only be
|
||||
/// `true` if the names won't be obvious from the source span.
|
||||
Module<Callable> _execute(Importer importer, Stylesheet stylesheet,
|
||||
{Map<String, _ConfiguredValue> configuration,
|
||||
bool namesInErrors = false}) {
|
||||
{Configuration configuration, bool namesInErrors = false}) {
|
||||
var url = stylesheet.span.sourceUrl;
|
||||
|
||||
var alreadyLoaded = _modules[url];
|
||||
if (alreadyLoaded != null) {
|
||||
if ((configuration ?? _configuration).isNotEmpty) {
|
||||
if (!(configuration ?? _configuration).isImplicit) {
|
||||
throw _exception(namesInErrors
|
||||
? "${p.prettyUri(url)} was already loaded, so it can't be "
|
||||
"configured using \"with\"."
|
||||
@ -640,10 +634,7 @@ class _EvaluateVisitor
|
||||
_atRootExcludingStyleRule = false;
|
||||
_inKeyframes = false;
|
||||
|
||||
if (configuration != null) {
|
||||
_configuration =
|
||||
configuration.isEmpty ? const {} : Map.of(configuration);
|
||||
}
|
||||
if (configuration != null) _configuration = configuration.clone();
|
||||
|
||||
visitStylesheet(stylesheet);
|
||||
css = _outOfOrderImports == null
|
||||
@ -664,14 +655,16 @@ class _EvaluateVisitor
|
||||
_atRootExcludingStyleRule = oldAtRootExcludingStyleRule;
|
||||
_inKeyframes = oldInKeyframes;
|
||||
|
||||
if (configuration != null && _configuration.isNotEmpty) {
|
||||
if (configuration != null &&
|
||||
!_configuration.isEmpty &&
|
||||
!_configuration.isImplicit) {
|
||||
throw _exception(
|
||||
namesInErrors
|
||||
? "\$${_configuration.keys.first} was not declared with "
|
||||
? "\$${_configuration.values.keys.first} was not declared with "
|
||||
"!default in the @used module."
|
||||
: "This variable was not declared with !default in the @used "
|
||||
"module.",
|
||||
_configuration.values.first.configurationSpan);
|
||||
_configuration.values.values.first.configurationSpan);
|
||||
}
|
||||
_configuration = oldConfiguration;
|
||||
});
|
||||
@ -1204,25 +1197,8 @@ class _EvaluateVisitor
|
||||
}
|
||||
|
||||
Value visitForwardRule(ForwardRule node) {
|
||||
// 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 oldConfiguration = _configuration;
|
||||
if (_configuration.isNotEmpty) {
|
||||
if (node.prefix != null) {
|
||||
_configuration = UnprefixedMapView(_configuration, node.prefix);
|
||||
}
|
||||
|
||||
if (node.shownVariables != null) {
|
||||
_configuration =
|
||||
LimitedMapView.whitelist(_configuration, node.shownVariables);
|
||||
} else if (node.hiddenVariables != null &&
|
||||
node.hiddenVariables.isNotEmpty) {
|
||||
_configuration =
|
||||
LimitedMapView.blacklist(_configuration, node.hiddenVariables);
|
||||
}
|
||||
}
|
||||
_configuration = _configuration.throughForward(node);
|
||||
|
||||
_loadModule(node.url, "@forward", node, (module) {
|
||||
_environment.forwardModule(module, node);
|
||||
@ -1304,6 +1280,7 @@ class _EvaluateVisitor
|
||||
var oldParent = _parent;
|
||||
var oldEndOfImports = _endOfImports;
|
||||
var oldOutOfOrderImports = _outOfOrderImports;
|
||||
var oldConfiguration = _configuration;
|
||||
_importer = importer;
|
||||
_stylesheet = stylesheet;
|
||||
_root = ModifiableCssStylesheet(stylesheet.span);
|
||||
@ -1311,6 +1288,12 @@ class _EvaluateVisitor
|
||||
_endOfImports = 0;
|
||||
_outOfOrderImports = null;
|
||||
|
||||
// This configuration is only used if it passes through a `@forward`
|
||||
// rule, so we avoid creating unnecessary ones for performance reasons.
|
||||
if (stylesheet.forwards.isNotEmpty) {
|
||||
_configuration = environment.toImplicitConfiguration();
|
||||
}
|
||||
|
||||
visitStylesheet(stylesheet);
|
||||
children = _addOutOfOrderImports();
|
||||
|
||||
@ -1320,6 +1303,7 @@ class _EvaluateVisitor
|
||||
_parent = oldParent;
|
||||
_endOfImports = oldEndOfImports;
|
||||
_outOfOrderImports = oldOutOfOrderImports;
|
||||
_configuration = oldConfiguration;
|
||||
});
|
||||
|
||||
// Create a dummy module with empty CSS and no extensions to make forwarded
|
||||
@ -1720,12 +1704,7 @@ class _EvaluateVisitor
|
||||
Value visitVariableDeclaration(VariableDeclaration node) {
|
||||
if (node.isGuarded) {
|
||||
if (node.namespace == null && _environment.atRoot) {
|
||||
// Explicitly check whether [_configuration] is empty because if it is,
|
||||
// it may be a constant map which doesn't support `remove()`.
|
||||
//
|
||||
// See also dart-lang/sdk#38540.
|
||||
var override =
|
||||
_configuration.isEmpty ? null : _configuration.remove(node.name);
|
||||
var override = _configuration.remove(node.name);
|
||||
if (override != null) {
|
||||
_addExceptionSpan(node, () {
|
||||
_environment.setVariable(
|
||||
@ -1771,14 +1750,14 @@ class _EvaluateVisitor
|
||||
_environment.addModule(module, namespace: node.namespace);
|
||||
},
|
||||
configuration: node.configuration.isEmpty
|
||||
? const {}
|
||||
: {
|
||||
? const Configuration.empty()
|
||||
: Configuration({
|
||||
for (var entry in node.configuration.entries)
|
||||
entry.key: _ConfiguredValue(
|
||||
entry.key: ConfiguredValue(
|
||||
entry.value.item1.accept(this).withoutSlash(),
|
||||
entry.value.item2,
|
||||
_expressionNode(entry.value.item1))
|
||||
});
|
||||
}));
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -2931,19 +2910,3 @@ class _ArgumentResults {
|
||||
_ArgumentResults(this.positional, this.named, this.separator,
|
||||
{this.positionalNodes, this.namedNodes});
|
||||
}
|
||||
|
||||
/// A variable value that's been configured using `@use ... with`.
|
||||
class _ConfiguredValue {
|
||||
/// The value of the variable.
|
||||
final Value value;
|
||||
|
||||
/// The span where the variable's configuration was written.
|
||||
final FileSpan configurationSpan;
|
||||
|
||||
/// The [AstNode] where the variable's value originated.
|
||||
///
|
||||
/// This is used to generate source maps.
|
||||
final AstNode assignmentNode;
|
||||
|
||||
_ConfiguredValue(this.value, this.configurationSpan, [this.assignmentNode]);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user