Skip to content

Commit

Permalink
Merge pull request #835 from sass/merge-use
Browse files Browse the repository at this point in the history
Merge and enable feature.use 馃帀
  • Loading branch information
nex3 committed Oct 1, 2019
2 parents 31acfd1 + 586a0eb commit b3cd44f
Show file tree
Hide file tree
Showing 28 changed files with 1,369 additions and 516 deletions.
41 changes: 41 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,44 @@
## 1.23.0

* **Launch the new Sass module system!** This adds:

* The [`@use` rule][], which loads Sass files as *modules* and makes their
members available only in the current file, with automatic namespacing.

[`@use` rule]: https://sass-lang.com/documentation/at-rules/use

* The [`@forward` rule][], which makes members of another Sass file available
to stylesheets that `@use` the current file.

[`@forward` rule]: https://sass-lang.com/documentation/at-rules/forward

* Built-in modules named `sass:color`, `sass:list`, `sass:map`, `sass:math`,
`sass:meta`, `sass:selector`, and `sass:string` that provide access to all
the built-in Sass functions you know and love, with automatic module
namespaces.

* The [`meta.load-css()` mixin][], which includes the CSS contents of a module
loaded from a (potentially dynamic) URL.

[`meta.load-css()` function]: https://sass-lang.com/documentation/modules/meta#load-css

* The [`meta.module-variables()` function][], which provides access to the
variables defined in a given module.

[`meta.module-variables()` function]: https://sass-lang.com/documentation/modules/meta#module-variables

* The [`meta.module-functions()` function][], which provides access to the
functions defined in a given module.

[`meta.module-functions()` function]: https://sass-lang.com/documentation/modules/meta#module-functions

Check out [the Sass blog][migrator blog] for more information on the new
module system. You can also use the new [Sass migrator][] to automatically
migrate your stylesheets to the new module system!

[migrator blog]: https://sass-lang.com/blog/7858341-the-module-system-is-launched
[Sass migrator]: https://sass-lang.com/documentation/cli/migrator

## 1.22.12

* **Potentially breaking bug fix:** character sequences consisting of two or
Expand Down
43 changes: 40 additions & 3 deletions lib/src/ast/sass/statement/use_rule.dart
Expand Up @@ -3,8 +3,12 @@
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';
import 'package:tuple/tuple.dart';

import '../../../logger.dart';
import '../../../parse/scss.dart';
import '../../../visitor/interface/statement.dart';
import '../expression.dart';
import '../expression/string.dart';
import '../statement.dart';

Expand All @@ -19,12 +23,45 @@ class UseRule implements Statement {
/// can be accessed without a namespace.
final String namespace;

/// A map from variable names to their values and the spans for those
/// variables, used to configure the loaded modules.
final Map<String, Tuple2<Expression, FileSpan>> configuration;

final FileSpan span;

UseRule(this.url, this.namespace, this.span);
UseRule(this.url, this.namespace, this.span,
{Map<String, Tuple2<Expression, FileSpan>> configuration})
: configuration = Map.unmodifiable(configuration ?? const {});

/// Parses a `@use` rule from [contents].
///
/// If passed, [url] is the name of the file from which [contents] comes.
///
/// Throws a [SassFormatException] if parsing fails.
factory UseRule.parse(String contents, {url, Logger logger}) =>
ScssParser(contents, url: url, logger: logger).parseUseRule();

T accept<T>(StatementVisitor<T> visitor) => visitor.visitUseRule(this);

String toString() => "@use ${StringExpression.quoteText(url.toString())} as "
"${namespace ?? "*"};";
String toString() {
var buffer =
StringBuffer("@use ${StringExpression.quoteText(url.toString())}");

var basename = url.pathSegments.isEmpty ? "" : url.pathSegments.last;
var dot = basename.indexOf(".");
if (namespace != basename.substring(0, dot == -1 ? basename.length : dot)) {
buffer.write(" as ${namespace ?? "*"}");
}

if (configuration.isNotEmpty) {
buffer.write(" with (");
buffer.write(configuration.entries
.map((entry) => "\$${entry.key}: ${entry.value.item1}")
.join(", "));
buffer.write(")");
}

buffer.write(";");
return buffer.toString();
}
}
8 changes: 2 additions & 6 deletions lib/src/ast/selector/placeholder.dart
Expand Up @@ -2,8 +2,7 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:charcode/charcode.dart';

import '../../util/character.dart' as character;
import '../../visitor/interface/selector.dart';
import '../selector.dart';

Expand All @@ -20,10 +19,7 @@ class PlaceholderSelector extends SimpleSelector {

/// Returns whether this is a private selector (that is, whether it begins
/// with `-` or `_`).
bool get isPrivate {
var start = name.codeUnitAt(0);
return start == $dash || start == $underscore;
}
bool get isPrivate => character.isPrivate(name);

PlaceholderSelector(this.name);

Expand Down
33 changes: 11 additions & 22 deletions lib/src/async_environment.dart
Expand Up @@ -48,9 +48,7 @@ class AsyncEnvironment {

/// A list of variables defined at each lexical scope level.
///
/// Each scope maps the names of declared variables to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
/// Each scope maps the names of declared variables to their values.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
Expand All @@ -67,45 +65,32 @@ class AsyncEnvironment {

/// A map of variable names to their indices in [_variables].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _variableIndices;

/// A list of functions defined at each lexical scope level.
///
/// Each scope maps the names of declared functions to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
/// Each scope maps the names of declared functions to their values.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
final List<Map<String, AsyncCallable>> _functions;

/// A map of function names to their indices in [_functions].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _functionIndices;

/// A list of mixins defined at each lexical scope level.
///
/// Each scope maps the names of declared mixins to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
/// Each scope maps the names of declared mixins to their values.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
final List<Map<String, AsyncCallable>> _mixins;

/// A map of mixin names to their indices in [_mixins].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _mixinIndices;

Expand Down Expand Up @@ -211,7 +196,7 @@ class AsyncEnvironment {
/// with the same name as a variable defined in this environment.
void addModule(Module module, {String namespace}) {
if (namespace == null) {
_globalModules ??= Set();
_globalModules ??= {};
_globalModules.add(module);
_allModules.add(module);

Expand Down Expand Up @@ -715,10 +700,14 @@ class AsyncEnvironment {
T value;
for (var module in _globalModules) {
var valueInModule = callback(module);
if (valueInModule != null && value != null) {
// TODO(nweiz): List the module URLs.
if (valueInModule == null) continue;

if (value != null) {
throw SassScriptException(
'This $type is available from multiple global modules.');
'This $type is available from multiple global modules:\n' +
bulletedList(_globalModules
.where((module) => callback(module) != null)
.map((module) => p.prettyUri(module.url))));
}

value = valueInModule;
Expand Down
35 changes: 12 additions & 23 deletions lib/src/environment.dart
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_environment.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 0e6357eae4a02ebe64d8cdbf48a14797f98a418c
// Checksum: 2522d5fbfb9d301b361a9bacac97228de2c6fd68
//
// ignore_for_file: unused_import

Expand Down Expand Up @@ -54,9 +54,7 @@ class Environment {

/// A list of variables defined at each lexical scope level.
///
/// Each scope maps the names of declared variables to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
/// Each scope maps the names of declared variables to their values.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
Expand All @@ -73,45 +71,32 @@ class Environment {

/// A map of variable names to their indices in [_variables].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _variableIndices;

/// A list of functions defined at each lexical scope level.
///
/// Each scope maps the names of declared functions to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
/// Each scope maps the names of declared functions to their values.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
final List<Map<String, Callable>> _functions;

/// A map of function names to their indices in [_functions].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _functionIndices;

/// A list of mixins defined at each lexical scope level.
///
/// Each scope maps the names of declared mixins to their values. These
/// maps are *normalized*, meaning that they treat hyphens and underscores in
/// its keys interchangeably.
/// Each scope maps the names of declared mixins to their values.
///
/// The first element is the global scope, and each successive element is
/// deeper in the tree.
final List<Map<String, Callable>> _mixins;

/// A map of mixin names to their indices in [_mixins].
///
/// This map is *normalized*, meaning that it treats hyphens and underscores
/// in its keys interchangeably.
///
/// This map is filled in as-needed, and may not be complete.
final Map<String, int> _mixinIndices;

Expand Down Expand Up @@ -217,7 +202,7 @@ class Environment {
/// with the same name as a variable defined in this environment.
void addModule(Module<Callable> module, {String namespace}) {
if (namespace == null) {
_globalModules ??= Set();
_globalModules ??= {};
_globalModules.add(module);
_allModules.add(module);

Expand Down Expand Up @@ -719,10 +704,14 @@ class Environment {
T value;
for (var module in _globalModules) {
var valueInModule = callback(module);
if (valueInModule != null && value != null) {
// TODO(nweiz): List the module URLs.
if (valueInModule == null) continue;

if (value != null) {
throw SassScriptException(
'This $type is available from multiple global modules.');
'This $type is available from multiple global modules:\n' +
bulletedList(_globalModules
.where((module) => callback(module) != null)
.map((module) => p.prettyUri(module.url))));
}

value = valueInModule;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/executable/options.dart
Expand Up @@ -138,8 +138,8 @@ class ExecutableOptions {
if (!_interactive) return false;

var invalidOptions = [
'stdin', 'indented', 'load-path', 'style', 'source-map', //
'source-map-urls', 'embed-sources', 'embed-source-map', 'update', 'watch'
'stdin', 'indented', 'style', 'source-map', 'source-map-urls', //
'embed-sources', 'embed-source-map', 'update', 'watch'
];
for (var option in invalidOptions) {
if (_options.wasParsed(option)) {
Expand Down
41 changes: 24 additions & 17 deletions lib/src/executable/repl.dart
Expand Up @@ -11,32 +11,38 @@ import 'package:stack_trace/stack_trace.dart';
import '../ast/sass.dart';
import '../exception.dart';
import '../executable/options.dart';
import '../import_cache.dart';
import '../importer/filesystem.dart';
import '../logger/tracking.dart';
import '../parse/parser.dart';
import '../value.dart' as internal;
import '../visitor/evaluate.dart';

/// Runs an interactive SassScript shell according to [options].
Future<void> repl(ExecutableOptions options) async {
var repl = Repl(prompt: '>> ');
var variables = <String, internal.Value>{};
var logger = TrackingLogger(options.logger);
var evaluator = Evaluator(
importer: FilesystemImporter('.'),
importCache:
ImportCache(const [], loadPaths: options.loadPaths, logger: logger),
logger: logger);
await for (String line in repl.runAsync()) {
if (line.trim().isEmpty) continue;
var logger = TrackingLogger(options.logger);
try {
VariableDeclaration declaration;
Expression expression;
if (line.startsWith("@")) {
evaluator.use(UseRule.parse(line, logger: logger));
continue;
}

if (Parser.isVariableDeclarationLike(line)) {
declaration = VariableDeclaration.parse(line, logger: logger);
expression = declaration.expression;
var declaration = VariableDeclaration.parse(line, logger: logger);
evaluator.setVariable(declaration);
print(evaluator.evaluate(VariableExpression(
declaration.name, declaration.span,
namespace: declaration.namespace)));
} else {
expression = Expression.parse(line, logger: logger);
print(evaluator.evaluate(Expression.parse(line, logger: logger)));
}

var result =
evaluateExpression(expression, variables: variables, logger: logger);
if (declaration != null) variables[declaration.name] = result;
print(result);
} on SassException catch (error, stackTrace) {
_logError(error, stackTrace, line, repl, options, logger);
}
Expand All @@ -46,10 +52,11 @@ Future<void> repl(ExecutableOptions options) async {
/// Logs an error from the interactive shell.
void _logError(SassException error, StackTrace stackTrace, String line,
Repl repl, ExecutableOptions options, TrackingLogger logger) {
// If something was logged after the input, just print the error.
if (!options.quiet && (logger.emittedDebug || logger.emittedWarning)) {
print("Error: ${error.message}");
print(error.span.highlight(color: options.color));
// If the error doesn't come from the repl line, or if something was logged
// after the user's input, just print the error normally.
if (error.span.sourceUrl != null ||
(!options.quiet && (logger.emittedDebug || logger.emittedWarning))) {
print(error.toString(color: options.color));
return;
}

Expand Down

0 comments on commit b3cd44f

Please sign in to comment.