Skip to content

Commit

Permalink
Merge pull request #2256 from sass/merge-main
Browse files Browse the repository at this point in the history
Merge origin/main into feature.color-4
  • Loading branch information
nex3 committed May 30, 2024
2 parents 6876968 + a59af7a commit d9d3092
Show file tree
Hide file tree
Showing 40 changed files with 1,197 additions and 232 deletions.
2 changes: 1 addition & 1 deletion .pubignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This should be identical to .gitignore except that it doesn't exclude
# generated protobuf files.
# generated Dart files.

.buildlog
.DS_Store
Expand Down
40 changes: 39 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 1.76.0
## 1.78.0

* **Breaking change**: Passing a number with unit `%` to the `$alpha` parameter
of `color.change()`, `color.adjust()`, `change-color()`, and `adjust-color()`
Expand Down Expand Up @@ -184,6 +184,44 @@

* Remove `RgbColor`, `HslColor` and `HwbColor` SassScript values.

## 1.77.3

### Dart API

* `Deprecation.duplicateVariableFlags` has been deprecated and replaced with
`Deprecation.duplicateVarFlags` to make it consistent with the
`duplicate-var-flags` name used on the command line and in the JS API.

## 1.77.2

* Don't emit deprecation warnings for functions and mixins beginning with `__`.

* Allow user-defined functions whose names begin with `_` and otherwise look
like vendor-prefixed functions with special CSS syntax.

### Command-Line Interface

* Properly handle the `--silence-deprecation` flag.

* Handle the `--fatal-deprecation` and `--future-deprecation` flags for
`--interactive` mode.

## 1.77.1

* Fix a crash that could come up with importers in certain contexts.

## 1.77.0

* *Don't* throw errors for at-rules in keyframe blocks.

## 1.76.0

* Throw errors for misplaced statements in keyframe blocks.

* Mixins and functions whose names begin with `--` are now deprecated for
forwards-compatibility with the in-progress CSS functions and mixins spec.
This deprecation is named `css-function-mixin`.

## 1.75.0

* Fix a bug in which stylesheet canonicalization could be cached incorrectly
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ A [Dart][dart] implementation of [Sass][sass]. **Sass makes CSS fun**.
* [Compatibility Policy](#compatibility-policy)
* [Browser Compatibility](#browser-compatibility)
* [Node.js Compatibility](#nodejs-compatibility)
* [Invalid CSS](#invalid-css)
* [Embedded Dart Sass](#embedded-dart-sass)
* [Usage](#usage)
* [Behavioral Differences from Ruby Sass](#behavioral-differences-from-ruby-sass)
Expand Down Expand Up @@ -405,6 +406,18 @@ considers itself free to break support if necessary.

[the Node.js release page]: https://nodejs.org/en/about/previous-releases

### Invalid CSS

Changes to the behavior of Sass stylesheets that produce invalid CSS output are
_not_ considered breaking changes. Such changes are almost always necessary when
adding support for new CSS features, and delaying all such features until a new
major version would be unduly burdensome for most users.

For example, when Sass began parsing `calc()` expressions, the invalid
expression `calc(1 +)` became a Sass error where before it was passed through
as-is. This was not considered a breaking change, because `calc(1 +)` was never
valid CSS to begin with.

## Embedded Dart Sass

Dart Sass includes an implementation of the compiler side of the [Embedded Sass
Expand Down
15 changes: 8 additions & 7 deletions lib/src/ast/sass/expression/function.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ final class FunctionExpression
/// without a namespace.
final String? namespace;

/// The name of the function being invoked, with underscores converted to
/// hyphens.
///
/// If this function is a plain CSS function, use [originalName] instead.
final String name;

/// The name of the function being invoked, with underscores left as-is.
final String originalName;

Expand All @@ -31,12 +37,6 @@ final class FunctionExpression

final FileSpan span;

/// The name of the function being invoked, with underscores converted to
/// hyphens.
///
/// If this function is a plain CSS function, use [originalName] instead.
String get name => originalName.replaceAll('_', '-');

FileSpan get nameSpan {
if (namespace == null) return span.initialIdentifier();
return span.withoutNamespace().initialIdentifier();
Expand All @@ -46,7 +46,8 @@ final class FunctionExpression
namespace == null ? null : span.initialIdentifier();

FunctionExpression(this.originalName, this.arguments, this.span,
{this.namespace});
{this.namespace})
: name = originalName.replaceAll('_', '-');

T accept<T>(ExpressionVisitor<T> visitor) =>
visitor.visitFunctionExpression(this);
Expand Down
10 changes: 7 additions & 3 deletions lib/src/ast/sass/statement/callable_declaration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ abstract base class CallableDeclaration
/// The name of this callable, with underscores converted to hyphens.
final String name;

/// The callable's original name, without underscores converted to hyphens.
final String originalName;

/// The comment immediately preceding this declaration.
final SilentComment? comment;

Expand All @@ -26,8 +29,9 @@ abstract base class CallableDeclaration

final FileSpan span;

CallableDeclaration(
this.name, this.arguments, Iterable<Statement> children, this.span,
CallableDeclaration(this.originalName, this.arguments,
Iterable<Statement> children, this.span,
{this.comment})
: super(List.unmodifiable(children));
: name = originalName.replaceAll('_', '-'),
super(List.unmodifiable(children));
}
9 changes: 7 additions & 2 deletions lib/src/ast/sass/statement/include_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ final class IncludeRule
/// hyphens.
final String name;

/// The original name of the mixin being invoked, without underscores
/// converted to hyphens.
final String originalName;

/// The arguments to pass to the mixin.
final ArgumentInvocation arguments;

Expand Down Expand Up @@ -55,8 +59,9 @@ final class IncludeRule
return startSpan.initialIdentifier();
}

IncludeRule(this.name, this.arguments, this.span,
{this.namespace, this.content});
IncludeRule(this.originalName, this.arguments, this.span,
{this.namespace, this.content})
: name = originalName.replaceAll('_', '-');

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

Expand Down
111 changes: 67 additions & 44 deletions lib/src/async_import_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import 'package:path/path.dart' as p;
import 'ast/sass.dart';
import 'deprecation.dart';
import 'importer.dart';
import 'importer/canonicalize_context.dart';
import 'importer/no_op.dart';
import 'importer/utils.dart';
import 'io.dart';
import 'logger.dart';
import 'util/map.dart';
import 'util/nullable.dart';
import 'utils.dart';

Expand Down Expand Up @@ -43,30 +45,28 @@ final class AsyncImportCache {
/// The `forImport` in each key is true when this canonicalization is for an
/// `@import` rule. Otherwise, it's for a `@use` or `@forward` rule.
///
/// This cache isn't used for relative imports, because they depend on the
/// specific base importer. That's stored separately in
/// [_relativeCanonicalizeCache].
/// This cache covers loads that go through the entire chain of [_importers],
/// but it doesn't cover individual loads or loads in which any importer
/// accesses `containingUrl`. See also [_perImporterCanonicalizeCache].
final _canonicalizeCache =
<(Uri, {bool forImport}), AsyncCanonicalizeResult?>{};

/// The canonicalized URLs for each non-canonical URL that's resolved using a
/// relative importer.
/// Like [_canonicalizeCache] but also includes the specific importer in the
/// key.
///
/// The map's keys have four parts:
/// This is used to cache both relative imports from the base importer and
/// individual importer results in the case where some other component of the
/// importer chain isn't cacheable.
final _perImporterCanonicalizeCache =
<(AsyncImporter, Uri, {bool forImport}), AsyncCanonicalizeResult?>{};

/// A map from the keys in [_perImporterCanonicalizeCache] that are generated
/// for relative URL loads agains the base importer to the original relative
/// URLs what were loaded.
///
/// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]).
/// 2. Whether the canonicalization is for an `@import` rule.
/// 3. The `baseImporter` passed to [canonicalize].
/// 4. The `baseUrl` passed to [canonicalize].
///
/// The map's values are the same as the return value of [canonicalize].
final _relativeCanonicalizeCache = <(
Uri, {
bool forImport,
AsyncImporter baseImporter,
Uri? baseUrl
}),
AsyncCanonicalizeResult?>{};
/// This is used to invalidate the cache when files are changed.
final _nonCanonicalRelativeUrls =
<(AsyncImporter, Uri, {bool forImport}), Uri>{};

/// The parsed stylesheets for each canonicalized import URL.
final _importCache = <Uri, Stylesheet?>{};
Expand Down Expand Up @@ -154,18 +154,17 @@ final class AsyncImportCache {
}

if (baseImporter != null && url.scheme == '') {
var relativeResult = await putIfAbsentAsync(_relativeCanonicalizeCache, (
url,
forImport: forImport,
baseImporter: baseImporter,
baseUrl: baseUrl
), () async {
var (result, cacheable) = await _canonicalize(
baseImporter, baseUrl?.resolveUri(url) ?? url, baseUrl, forImport);
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var key = (baseImporter, resolvedUrl, forImport: forImport);
var relativeResult =
await putIfAbsentAsync(_perImporterCanonicalizeCache, key, () async {
var (result, cacheable) =
await _canonicalize(baseImporter, resolvedUrl, baseUrl, forImport);
assert(
cacheable,
"Relative loads should always be cacheable because they never "
"provide access to the containing URL.");
if (baseUrl != null) _nonCanonicalRelativeUrls[key] = url;
return result;
});
if (relativeResult != null) return relativeResult;
Expand All @@ -181,17 +180,41 @@ final class AsyncImportCache {
// `canonicalize()` calls we've attempted are cacheable. Only if they are do
// we store the result in the cache.
var cacheable = true;
for (var importer in _importers) {
for (var i = 0; i < _importers.length; i++) {
var importer = _importers[i];
var perImporterKey = (importer, url, forImport: forImport);
switch (_perImporterCanonicalizeCache.getOption(perImporterKey)) {
case (var result?,):
return result;
case (null,):
continue;
}

switch (await _canonicalize(importer, url, baseUrl, forImport)) {
case (var result?, true) when cacheable:
_canonicalizeCache[key] = result;
return result;

case (var result?, _):
return result;

case (_, false):
cacheable = false;
case (var result, true) when !cacheable:
_perImporterCanonicalizeCache[perImporterKey] = result;
if (result != null) return result;

case (var result, false):
if (cacheable) {
// If this is the first uncacheable result, add all previous results
// to the per-importer cache so we don't have to re-run them for
// future uses of this importer.
for (var j = 0; j < i; j++) {
_perImporterCanonicalizeCache[(
_importers[j],
url,
forImport: forImport
)] = null;
}
cacheable = false;
}

if (result != null) return result;
}
}

Expand All @@ -206,18 +229,17 @@ final class AsyncImportCache {
/// that result is cacheable at all.
Future<(AsyncCanonicalizeResult?, bool cacheable)> _canonicalize(
AsyncImporter importer, Uri url, Uri? baseUrl, bool forImport) async {
var canonicalize = forImport
? () => inImportRule(() => importer.canonicalize(url))
: () => importer.canonicalize(url);

var passContainingUrl = baseUrl != null &&
(url.scheme == '' || await importer.isNonCanonicalScheme(url.scheme));
var result = await withContainingUrl(
passContainingUrl ? baseUrl : null, canonicalize);

// TODO(sass/dart-sass#2208): Determine whether the containing URL was
// _actually_ accessed rather than assuming it was.
var cacheable = !passContainingUrl || importer is FilesystemImporter;
var canonicalizeContext =
CanonicalizeContext(passContainingUrl ? baseUrl : null, forImport);

var result = await withCanonicalizeContext(
canonicalizeContext, () => importer.canonicalize(url));

var cacheable =
!passContainingUrl || !canonicalizeContext.wasContainingUrlAccessed;

if (result == null) return (null, cacheable);

Expand Down Expand Up @@ -315,7 +337,7 @@ final class AsyncImportCache {
Uri sourceMapUrl(Uri canonicalUrl) =>
_resultsCache[canonicalUrl]?.sourceMapUrl ?? canonicalUrl;

/// Clears the cached canonical version of the given [url].
/// Clears the cached canonical version of the given non-canonical [url].
///
/// Has no effect if the canonical version of [url] has not been cached.
///
Expand All @@ -324,7 +346,8 @@ final class AsyncImportCache {
void clearCanonicalize(Uri url) {
_canonicalizeCache.remove((url, forImport: false));
_canonicalizeCache.remove((url, forImport: true));
_relativeCanonicalizeCache.removeWhere((key, _) => key.$1 == url);
_perImporterCanonicalizeCache.removeWhere(
(key, _) => key.$2 == url || _nonCanonicalRelativeUrls[key] == url);
}

/// Clears the cached parse tree for the stylesheet with the given
Expand Down
Loading

0 comments on commit d9d3092

Please sign in to comment.