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

Add support for arbitrary modifiers after @import #1695

Merged
merged 3 commits into from May 19, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -183,11 +183,11 @@ jobs:
- uses: dart-lang/setup-dart@v1
- run: dart pub get
- name: dartdoc sass
run: dartdoc --quiet --no-generate-docs
run: dart run dartdoc --quiet --no-generate-docs
--errors ambiguous-doc-reference,broken-link,deprecated
--errors unknown-directive,unknown-macro,unresolved-doc-reference
- name: dartdoc sass_api
run: cd pkg/sass_api && dartdoc --quiet --no-generate-docs
run: cd pkg/sass_api && dart run dartdoc --quiet --no-generate-docs
--errors ambiguous-doc-reference,broken-link,deprecated
--errors unknown-directive,unknown-macro,unresolved-doc-reference

Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
@@ -1,7 +1,13 @@
## 1.51.1
## 1.52.0

* Add support for arbitrary modifiers at the end of plain CSS imports, in
addition to the existing `supports()` and media queries. Sass now allows any
sequence of identifiers of functions after the URL of an import for forwards
compatibility with future additions to the CSS spec.

* Fix an issue where source locations tracked through variable references could
potentially become incorrect.

* Fix a bug where a loud comment in the source can break the source map when
embedding the sources, when using the command-line interface or the legacy JS
API.
Expand Down
8 changes: 2 additions & 6 deletions lib/src/ast/css/import.dart
Expand Up @@ -3,7 +3,6 @@
// https://opensource.org/licenses/MIT.

import '../../visitor/interface/css.dart';
import 'media_query.dart';
import 'node.dart';
import 'value.dart';

Expand All @@ -14,11 +13,8 @@ abstract class CssImport extends CssNode {
/// This includes quotes.
CssValue<String> get url;

/// The supports condition attached to this import.
CssValue<String>? get supports;

/// The media query attached to this import.
List<CssMediaQuery>? get media;
/// The modifiers (such as media or supports queries) attached to this import.
CssValue<String>? get modifiers;

T accept<T>(CssVisitor<T> visitor) => visitor.visitCssImport(this);
}
11 changes: 2 additions & 9 deletions lib/src/ast/css/modifiable/import.dart
Expand Up @@ -6,7 +6,6 @@ import 'package:source_span/source_span.dart';

import '../../../visitor/interface/modifiable_css.dart';
import '../import.dart';
import '../media_query.dart';
import '../value.dart';
import 'node.dart';

Expand All @@ -17,17 +16,11 @@ class ModifiableCssImport extends ModifiableCssNode implements CssImport {
/// This includes quotes.
final CssValue<String> url;

/// The supports condition attached to this import.
final CssValue<String>? supports;

/// The media query attached to this import.
final List<CssMediaQuery>? media;
final CssValue<String>? modifiers;

final FileSpan span;

ModifiableCssImport(this.url, this.span,
{this.supports, Iterable<CssMediaQuery>? media})
: media = media == null ? null : List.unmodifiable(media);
ModifiableCssImport(this.url, this.span, {this.modifiers});

T accept<T>(ModifiableCssVisitor<T> visitor) => visitor.visitCssImport(this);
}
20 changes: 5 additions & 15 deletions lib/src/ast/sass/import/static.dart
Expand Up @@ -7,7 +7,6 @@ import 'package:source_span/source_span.dart';

import '../import.dart';
import '../interpolation.dart';
import '../supports_condition.dart';

/// An import that produces a plain CSS `@import` rule.
///
Expand All @@ -19,22 +18,13 @@ class StaticImport implements Import {
/// This already contains quotes.
final Interpolation url;

/// The supports condition attached to this import, or `null` if no condition
/// is attached.
final SupportsCondition? supports;

/// The media query attached to this import, or `null` if no condition is
/// attached.
final Interpolation? media;
/// The modifiers (such as media or supports queries) attached to this import,
/// or `null` if none are attached.
final Interpolation? modifiers;

final FileSpan span;

StaticImport(this.url, this.span, {this.supports, this.media});
StaticImport(this.url, this.span, {this.modifiers});

String toString() {
var buffer = StringBuffer(url);
if (supports != null) buffer.write(" supports($supports)");
if (media != null) buffer.write(" $media");
return buffer.toString();
}
String toString() => "$url${modifiers == null ? '' : ' $modifiers'}";
}
23 changes: 23 additions & 0 deletions lib/src/ast/sass/interpolation.dart
Expand Up @@ -5,6 +5,7 @@
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../interpolation_buffer.dart';
import 'expression.dart';
import 'node.dart';

Expand Down Expand Up @@ -40,6 +41,28 @@ class Interpolation implements SassNode {
return first is String ? first : '';
}

/// Creates a new [Interpolation] by concatenating a sequence of [String]s,
/// [Expression]s, or nested [Interpolation]s.
static Interpolation concat(
Iterable<Object /* String | Expression | Interpolation */ > contents,
FileSpan span) {
var buffer = InterpolationBuffer();
for (var element in contents) {
if (element is String) {
buffer.write(element);
} else if (element is Expression) {
buffer.add(element);
} else if (element is Interpolation) {
buffer.addInterpolation(element);
} else {
throw ArgumentError.value(contents, "contents",
"May only contains Strings, Expressions, or Interpolations.");
}
}

return buffer.interpolation(span);
}

Interpolation(Iterable<Object /* String | Expression */ > contents, this.span)
: contents = List.unmodifiable(contents) {
for (var i = 0; i < this.contents.length; i++) {
Expand Down
7 changes: 6 additions & 1 deletion lib/src/ast/sass/supports_condition.dart
Expand Up @@ -4,10 +4,15 @@

import 'package:meta/meta.dart';

import 'interpolation.dart';
import 'node.dart';

/// An abstract class for defining the condition a `@supports` rule selects.
///
/// {@category AST}
@sealed
abstract class SupportsCondition extends SassNode {}
abstract class SupportsCondition extends SassNode {
/// Returns an [Interpolation] that evaluates to the same value as this
/// [SupportsCondition].
Interpolation toInterpolation();
}
3 changes: 3 additions & 0 deletions lib/src/ast/sass/supports_condition/anything.dart
Expand Up @@ -21,5 +21,8 @@ class SupportsAnything implements SupportsCondition {

SupportsAnything(this.contents, this.span);

Interpolation toInterpolation() =>
Interpolation.concat(['(', contents, ')'], span);
Copy link
Contributor

Choose a reason for hiding this comment

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

is it actually equivalent in case a CalculationExpression is present in it ? We have a special handling for calculations in supports conditions, that would be lost here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, good catch. Instead of converting these to interpolation, I've added an expression-level AST node just for this.


String toString() => "($contents)";
}
4 changes: 4 additions & 0 deletions lib/src/ast/sass/supports_condition/declaration.dart
Expand Up @@ -7,6 +7,7 @@ import 'package:source_span/source_span.dart';

import '../expression.dart';
import '../expression/string.dart';
import '../interpolation.dart';
import '../supports_condition.dart';

/// A condition that selects for browsers where a given declaration is
Expand Down Expand Up @@ -42,5 +43,8 @@ class SupportsDeclaration implements SupportsCondition {

SupportsDeclaration(this.name, this.value, this.span);

Interpolation toInterpolation() => Interpolation.concat(
['(', name, isCustomProperty ? ':' : ': ', value, ')'], span);

String toString() => "($name: $value)";
}
3 changes: 3 additions & 0 deletions lib/src/ast/sass/supports_condition/function.dart
Expand Up @@ -23,5 +23,8 @@ class SupportsFunction implements SupportsCondition {

SupportsFunction(this.name, this.arguments, this.span);

Interpolation toInterpolation() =>
Interpolation.concat([name, '(', arguments, ')'], span);

String toString() => "$name($arguments)";
}
3 changes: 3 additions & 0 deletions lib/src/ast/sass/supports_condition/interpolation.dart
Expand Up @@ -6,6 +6,7 @@ import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../expression.dart';
import '../interpolation.dart';
import '../supports_condition.dart';

/// An interpolated condition.
Expand All @@ -20,5 +21,7 @@ class SupportsInterpolation implements SupportsCondition {

SupportsInterpolation(this.expression, this.span);

Interpolation toInterpolation() => Interpolation([expression], span);

String toString() => "#{$expression}";
}
12 changes: 12 additions & 0 deletions lib/src/ast/sass/supports_condition/negation.dart
Expand Up @@ -5,6 +5,7 @@
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../interpolation.dart';
import '../supports_condition.dart';
import 'operation.dart';

Expand All @@ -20,6 +21,17 @@ class SupportsNegation implements SupportsCondition {

SupportsNegation(this.condition, this.span);

Interpolation toInterpolation() {
var needsParens =
condition is SupportsNegation || condition is SupportsOperation;
return Interpolation.concat([
"not ",
if (needsParens) "(",
condition.toInterpolation(),
if (needsParens) ")"
], span);
}

String toString() {
if (condition is SupportsNegation || condition is SupportsOperation) {
return "not ($condition)";
Expand Down
20 changes: 18 additions & 2 deletions lib/src/ast/sass/supports_condition/operation.dart
Expand Up @@ -5,6 +5,7 @@
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../interpolation.dart';
import '../supports_condition.dart';
import 'negation.dart';

Expand Down Expand Up @@ -34,10 +35,25 @@ class SupportsOperation implements SupportsCondition {
}
}

Interpolation toInterpolation() => Interpolation.concat([
..._parenthesizeInterpolation(left),
" $operator ",
..._parenthesizeInterpolation(right)
], span);

/// Returns a list that can be passed to [Interpolation.concat], with
/// parentheses around [condition] if necessary.
List<Object /* String | Expression | Interpolation */ >
_parenthesizeInterpolation(SupportsCondition condition) => condition
is SupportsNegation ||
(condition is SupportsOperation && condition.operator == operator)
? ["(", condition.toInterpolation(), ")"]
: [condition.toInterpolation()];

String toString() =>
"${_parenthesize(left)} $operator ${_parenthesize(right)}";
"${_parenthesizeString(left)} $operator ${_parenthesizeString(right)}";

String _parenthesize(SupportsCondition condition) =>
String _parenthesizeString(SupportsCondition condition) =>
condition is SupportsNegation ||
(condition is SupportsOperation && condition.operator == operator)
? "($condition)"
Expand Down
4 changes: 2 additions & 2 deletions lib/src/parse/css.dart
Expand Up @@ -94,11 +94,11 @@ class CssParser extends ScssParser {
var urlSpan = scanner.spanFrom(urlStart);

whitespace();
var queries = tryImportQueries();
var modifiers = tryImportModifiers();
expectStatementSeparator("@import rule");
return ImportRule([
StaticImport(Interpolation([url], urlSpan), scanner.spanFrom(urlStart),
supports: queries?.item1, media: queries?.item2)
modifiers: modifiers)
], scanner.spanFrom(start));
}

Expand Down