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

Convert Sass to Dart 3 style #2038

Merged
merged 6 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
40 changes: 20 additions & 20 deletions bin/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import 'dart:isolate';

import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;
import 'package:stack_trace/stack_trace.dart';
import 'package:term_glyph/term_glyph.dart' as term_glyph;
Expand All @@ -19,6 +18,7 @@ import 'package:sass/src/io.dart';
import 'package:sass/src/io.dart' as io;
import 'package:sass/src/logger/deprecation_handling.dart';
import 'package:sass/src/stylesheet_graph.dart';
import 'package:sass/src/util/map.dart';
import 'package:sass/src/utils.dart';
import 'package:sass/src/embedded/executable.dart'
// Never load the embedded protocol when compiling to JS.
Expand Down Expand Up @@ -47,8 +47,8 @@ Future<void> main(List<String> args) async {
io.printError(buffer);
}

if (args.firstOrNull == '--embedded') {
embedded.main(args.sublist(1));
if (args case ['--embedded', ...var rest]) {
embedded.main(rest);
return;
}

Expand Down Expand Up @@ -84,25 +84,14 @@ Future<void> main(List<String> args) async {
return;
}

for (var source in options.sourcesToDestinations.keys) {
var destination = options.sourcesToDestinations[source];
for (var (source, destination) in options.sourcesToDestinations.pairs) {
try {
await compileStylesheet(options, graph, source, destination,
ifModified: options.update);
} on SassException catch (error, stackTrace) {
// This is an immediately-invoked function expression to work around
// dart-lang/sdk#33400.
() {
try {
if (destination != null &&
// dart-lang/sdk#45348
!options!.emitErrorCss) {
deleteFile(destination);
}
} on FileSystemException {
// If the file doesn't exist, that's fine.
}
}();
if (destination != null && !options.emitErrorCss) {
_tryDelete(destination);
}

printError(error.toString(color: options.color),
options.trace ? getTrace(error) ?? stackTrace : null);
Expand Down Expand Up @@ -134,9 +123,9 @@ Future<void> main(List<String> args) async {
exitCode = 64;
} catch (error, stackTrace) {
var buffer = StringBuffer();
if (options != null && options.color) buffer.write('\u001b[31m\u001b[1m');
if (options?.color ?? false) buffer.write('\u001b[31m\u001b[1m');
buffer.write('Unexpected exception:');
if (options != null && options.color) buffer.write('\u001b[0m');
if (options?.color ?? false) buffer.write('\u001b[0m');
buffer.writeln();
buffer.writeln(error);

Expand Down Expand Up @@ -165,3 +154,14 @@ Future<String> _loadVersion() async {
.split(" ")
.last;
}

/// Delete [path] if it exists and do nothing otherwise.
///
/// This is a separate function to work around dart-lang/sdk#53082.
void _tryDelete(String path) {
try {
deleteFile(path);
} on FileSystemException {
// If the file doesn't exist, that's fine.
}
}
5 changes: 1 addition & 4 deletions lib/src/ast/css/at_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

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

/// An unknown plain CSS at-rule.
abstract class CssAtRule extends CssParentNode {
abstract interface class CssAtRule implements CssParentNode {
/// The name of this rule.
CssValue<String> get name;

Expand All @@ -19,6 +18,4 @@ abstract class CssAtRule extends CssParentNode {
/// This implies `children.isEmpty`, but the reverse is not true—for a rule
/// like `@foo {}`, [children] is empty but [isChildless] is `false`.
bool get isChildless;

T accept<T>(CssVisitor<T> visitor) => visitor.visitCssAtRule(this);
}
5 changes: 1 addition & 4 deletions lib/src/ast/css/comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

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

/// A plain CSS comment.
///
/// This is always a multi-line comment.
abstract class CssComment extends CssNode {
abstract interface class CssComment implements CssNode {
/// The contents of this comment, including `/*` and `*/`.
String get text;

/// Whether this comment starts with `/*!` and so should be preserved even in
/// compressed mode.
bool get isPreserved;

T accept<T>(CssVisitor<T> visitor) => visitor.visitCssComment(this);
}
5 changes: 1 addition & 4 deletions lib/src/ast/css/declaration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
import 'package:source_span/source_span.dart';

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

/// A plain CSS declaration (that is, a `name: value` pair).
abstract class CssDeclaration extends CssNode {
abstract interface class CssDeclaration implements CssNode {
/// The name of this declaration.
CssValue<String> get name;

Expand All @@ -34,6 +33,4 @@ abstract class CssDeclaration extends CssNode {
/// If this is `true`, [isCustomProperty] will also be `true` and [value] will
/// contain a [SassString].
bool get parsedAsCustomProperty;

T accept<T>(CssVisitor<T> visitor);
}
5 changes: 1 addition & 4 deletions lib/src/ast/css/import.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

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

/// A plain CSS `@import`.
abstract class CssImport extends CssNode {
abstract interface class CssImport implements CssNode {
/// The URL being imported.
///
/// This includes quotes.
CssValue<String> get url;

/// 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);
}
5 changes: 1 addition & 4 deletions lib/src/ast/css/keyframe_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

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

/// A block within a `@keyframes` rule.
///
/// For example, `10% {opacity: 0.5}`.
abstract class CssKeyframeBlock extends CssParentNode {
abstract interface class CssKeyframeBlock implements CssParentNode {
/// The selector for this block.
CssValue<List<String>> get selector;

T accept<T>(CssVisitor<T> visitor) => visitor.visitCssKeyframeBlock(this);
}
18 changes: 7 additions & 11 deletions lib/src/ast/css/media_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import '../../parse/media_query.dart';
import '../../utils.dart';

/// A plain CSS media query, as used in `@media` and `@import`.
class CssMediaQuery {
final class CssMediaQuery {
/// The modifier, probably either "not" or "only".
///
/// This may be `null` if no modifier is in use.
Expand Down Expand Up @@ -197,25 +197,21 @@ class CssMediaQuery {
///
/// This is either the singleton values [empty] or [unrepresentable], or an
/// instance of [MediaQuerySuccessfulMergeResult].
abstract class MediaQueryMergeResult {
sealed class MediaQueryMergeResult {
/// A singleton value indicating that there are no contexts that match both
/// input queries.
static const empty = _SingletonCssMediaQueryMergeResult("empty");
static const empty = _SingletonCssMediaQueryMergeResult.empty;

/// A singleton value indicating that the contexts that match both input
/// queries can't be represented by a Level 3 media query.
static const unrepresentable =
_SingletonCssMediaQueryMergeResult("unrepresentable");
_SingletonCssMediaQueryMergeResult.unrepresentable;
}

/// The subclass [MediaQueryMergeResult] that represents singleton enum values.
class _SingletonCssMediaQueryMergeResult implements MediaQueryMergeResult {
/// The name of the result type.
final String _name;

const _SingletonCssMediaQueryMergeResult(this._name);

String toString() => _name;
enum _SingletonCssMediaQueryMergeResult implements MediaQueryMergeResult {
empty,
unrepresentable;
}

/// A successful result of [CssMediaQuery.merge].
Expand Down
5 changes: 1 addition & 4 deletions lib/src/ast/css/media_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

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

/// A plain CSS `@media` rule.
abstract class CssMediaRule extends CssParentNode {
abstract interface class CssMediaRule implements CssParentNode {
/// The queries for this rule.
///
/// This is never empty.
List<CssMediaQuery> get queries;

T accept<T>(CssVisitor<T> visitor) => visitor.visitCssMediaRule(this);
}
3 changes: 2 additions & 1 deletion lib/src/ast/css/modifiable/at_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import '../value.dart';
import 'node.dart';

/// A modifiable version of [CssAtRule] for use in the evaluation step.
class ModifiableCssAtRule extends ModifiableCssParentNode implements CssAtRule {
final class ModifiableCssAtRule extends ModifiableCssParentNode
implements CssAtRule {
final CssValue<String> name;
final CssValue<String>? value;
final bool isChildless;
Expand Down
3 changes: 2 additions & 1 deletion lib/src/ast/css/modifiable/comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import '../comment.dart';
import 'node.dart';

/// A modifiable version of [CssComment] for use in the evaluation step.
class ModifiableCssComment extends ModifiableCssNode implements CssComment {
final class ModifiableCssComment extends ModifiableCssNode
implements CssComment {
final String text;
final FileSpan span;

Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/modifiable/declaration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import '../value.dart';
import 'node.dart';

/// A modifiable version of [CssDeclaration] for use in the evaluation step.
class ModifiableCssDeclaration extends ModifiableCssNode
final class ModifiableCssDeclaration extends ModifiableCssNode
implements CssDeclaration {
final CssValue<String> name;
final CssValue<Value> value;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/modifiable/import.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import '../value.dart';
import 'node.dart';

/// A modifiable version of [CssImport] for use in the evaluation step.
class ModifiableCssImport extends ModifiableCssNode implements CssImport {
final class ModifiableCssImport extends ModifiableCssNode implements CssImport {
/// The URL being imported.
///
/// This includes quotes.
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/modifiable/keyframe_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import '../value.dart';
import 'node.dart';

/// A modifiable version of [CssKeyframeBlock] for use in the evaluation step.
class ModifiableCssKeyframeBlock extends ModifiableCssParentNode
final class ModifiableCssKeyframeBlock extends ModifiableCssParentNode
implements CssKeyframeBlock {
final CssValue<List<String>> selector;
final FileSpan span;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/modifiable/media_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import '../media_rule.dart';
import 'node.dart';

/// A modifiable version of [CssMediaRule] for use in the evaluation step.
class ModifiableCssMediaRule extends ModifiableCssParentNode
final class ModifiableCssMediaRule extends ModifiableCssParentNode
implements CssMediaRule {
final List<CssMediaQuery> queries;
final FileSpan span;
Expand Down
7 changes: 3 additions & 4 deletions lib/src/ast/css/modifiable/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import '../node.dart';
/// Almost all CSS nodes are the modifiable classes under the covers. However,
/// modification should only be done within the evaluation step, so the
/// unmodifiable types are used elsewhere to enforce that constraint.
abstract class ModifiableCssNode extends CssNode {
abstract base class ModifiableCssNode extends CssNode {
/// The node that contains this, or `null` for the root [CssStylesheet] node.
ModifiableCssParentNode? get parent => _parent;
ModifiableCssParentNode? _parent;
Expand Down Expand Up @@ -43,16 +43,15 @@ abstract class ModifiableCssNode extends CssNode {
}

parent._children.removeAt(_indexInParent!);
for (var i = _indexInParent!; i < parent._children.length; i++) {
var child = parent._children[i];
for (var child in parent._children.skip(_indexInParent!)) {
child._indexInParent = child._indexInParent! - 1;
}
_parent = null;
}
}

/// A modifiable version of [CssParentNode] for use in the evaluation step.
abstract class ModifiableCssParentNode extends ModifiableCssNode
abstract base class ModifiableCssParentNode extends ModifiableCssNode
implements CssParentNode {
final List<ModifiableCssNode> children;
final List<ModifiableCssNode> _children;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/modifiable/style_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import '../style_rule.dart';
import 'node.dart';

/// A modifiable version of [CssStyleRule] for use in the evaluation step.
class ModifiableCssStyleRule extends ModifiableCssParentNode
final class ModifiableCssStyleRule extends ModifiableCssParentNode
implements CssStyleRule {
SelectorList get selector => _selector.value;

Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/modifiable/stylesheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import '../stylesheet.dart';
import 'node.dart';

/// A modifiable version of [CssStylesheet] for use in the evaluation step.
class ModifiableCssStylesheet extends ModifiableCssParentNode
final class ModifiableCssStylesheet extends ModifiableCssParentNode
implements CssStylesheet {
final FileSpan span;

Expand Down
2 changes: 1 addition & 1 deletion lib/src/ast/css/modifiable/supports_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import '../value.dart';
import 'node.dart';

/// A modifiable version of [CssSupportsRule] for use in the evaluation step.
class ModifiableCssSupportsRule extends ModifiableCssParentNode
final class ModifiableCssSupportsRule extends ModifiableCssParentNode
implements CssSupportsRule {
final CssValue<String> condition;
final FileSpan span;
Expand Down
6 changes: 4 additions & 2 deletions lib/src/ast/css/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import 'comment.dart';
import 'style_rule.dart';

/// A statement in a plain CSS syntax tree.
abstract class CssNode extends AstNode {
@sealed
Copy link
Member

Choose a reason for hiding this comment

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

Just to confirm my own understanding: this uses the @sealed annotation rather than the sealed keyword because the former allows extensions within the same package while the latter only allows extensions within the same library, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. See also dart-lang/language#3113

abstract class CssNode implements AstNode {
/// Whether this was generated from the last node in a nested Sass tree that
/// got flattened during evaluation.
bool get isGroupEnd;
Expand Down Expand Up @@ -43,12 +44,13 @@ abstract class CssNode extends AstNode {
bool get isInvisibleHidingComments => accept(
const _IsInvisibleVisitor(includeBogus: true, includeComments: true));

String toString() => serialize(this, inspect: true).css;
String toString() => serialize(this, inspect: true).$1;
}

// NOTE: New at-rule implementations should add themselves to [AtRootRule]'s
// exclude logic.
/// A [CssNode] that can have child statements.
@sealed
abstract class CssParentNode extends CssNode {
/// The child statements of this node.
List<CssNode> get children;
Expand Down