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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge origin/main into feature.color-4 #2256

Merged
merged 16 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


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