Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge and enable feature.use 馃帀 #835

Merged
merged 27 commits into from Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
18b06df
Merge remote-tracking branch 'origin/master' into merge-master
nex3 Jun 26, 2019
ccec934
Merge pull request #736 from sass/merge-master
nex3 Jun 26, 2019
6f89055
Add support for "@use with" (#728)
nex3 Jun 27, 2019
fe30fd6
Add the ability for built-in modules to expose mixins
nex3 Jun 13, 2019
bfbe1ef
Add a load-css() function
nex3 Jun 27, 2019
b8186fe
Merge remote-tracking branch 'origin/master' into merge-master
nex3 Jun 28, 2019
3c3fd1f
Merge pull request #746 from sass/merge-master
nex3 Jun 29, 2019
72d9328
Merge remote-tracking branch 'origin/feature.use' into load-css
nex3 Jun 29, 2019
88774b3
Fix a Dart 2.4 analysis hint
nex3 Jun 29, 2019
1fb3d1f
Merge pull request #741 from sass/load-css
nex3 Jun 29, 2019
437e04e
Merge remote-tracking branch 'origin/master' into feature.use
nex3 Jul 17, 2019
c0f51c2
Merge pull request #765 from sass/merge-master
nex3 Jul 17, 2019
e454647
Improve the error message for a global member conflict (#762)
nex3 Jul 18, 2019
6775490
Merge remote-tracking branch 'origin/master' into merge-master
nex3 Jul 26, 2019
5629f59
Merge pull request #782 from sass/merge-master
nex3 Jul 29, 2019
5349042
Allow @use in the REPL (#784)
nex3 Jul 29, 2019
8dea51d
Change variable syntax to namespace.$name (#803)
nex3 Aug 29, 2019
75c0452
Merge branch 'master' into feature.use
nex3 Sep 2, 2019
cfd5cd7
Merge pull request #817 from sass/merge-master
nex3 Sep 3, 2019
93a3a6f
Merge branch 'master' into feature.use
nex3 Sep 24, 2019
7bfbba0
Merge pull request #830 from sass/merge-master
nex3 Sep 26, 2019
75305a1
Always use a non-null map for _EvaluateVisitor._configuration (#827)
nex3 Sep 27, 2019
7d34406
Remove adjust-hue() from sass:color (#828)
nex3 Sep 27, 2019
fa3c657
Support multiple global uses in one file (#833)
nex3 Sep 27, 2019
97203f1
Merge branch 'feature.use'
nex3 Oct 1, 2019
5052350
Bump the pubspec and add a changelog entry for @use
nex3 Oct 1, 2019
586a0eb
Code review change
nex3 Oct 1, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to warn you that the link for the migrator isn't live yet.


## 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