Skip to content
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
15 changes: 15 additions & 0 deletions lib/src/ast/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ export 'sass/expression/variable.dart';
export 'sass/import.dart';
export 'sass/import/dynamic.dart';
export 'sass/import/static.dart';
export 'sass/interpolated_selector.dart';
export 'sass/interpolated_selector/attribute.dart';
export 'sass/interpolated_selector/class.dart';
export 'sass/interpolated_selector/complex_component.dart';
export 'sass/interpolated_selector/complex.dart';
export 'sass/interpolated_selector/compound.dart';
export 'sass/interpolated_selector/id.dart';
export 'sass/interpolated_selector/list.dart';
export 'sass/interpolated_selector/parent.dart';
export 'sass/interpolated_selector/placeholder.dart';
export 'sass/interpolated_selector/pseudo.dart';
export 'sass/interpolated_selector/qualified_name.dart';
export 'sass/interpolated_selector/simple.dart';
export 'sass/interpolated_selector/type.dart';
export 'sass/interpolated_selector/universal.dart';
export 'sass/interpolation.dart';
export 'sass/node.dart';
export 'sass/parameter.dart';
Expand Down
20 changes: 20 additions & 0 deletions lib/src/ast/sass/interpolated_selector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

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

// Note: this has to be a concrete class so we can expose its accept() function
// to the JS parser.

/// A simple selector before interoplation is resolved.
///
/// Unlike [Selector], this is parsed during the initial stylesheet parse
/// when `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
abstract base class InterpolatedSelector implements SassNode {
/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor);
}
71 changes: 71 additions & 0 deletions lib/src/ast/sass/interpolated_selector/attribute.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../../visitor/interface/interpolated_selector.dart';
import '../../css/value.dart';
import '../../sass/interpolation.dart';
import '../../selector.dart';
import 'qualified_name.dart';
import 'simple.dart';

/// An attribute selector.
///
/// Unlike [AttributeSelector], this is parsed during the initial stylesheet
/// parse when `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedAttributeSelector extends InterpolatedSimpleSelector {
/// The name of the attribute being selected for.
final InterpolatedQualifiedName name;

/// The operator that defines the semantics of [value].
///
/// This is `null` if and only if [value] is `null`.
final CssValue<AttributeOperator>? op;

/// An assertion about the value of [name].
///
/// This is `null` if and only if [op] is `null`.
final Interpolation? value;

/// The modifier which indicates how the attribute selector should be
/// processed.
///
/// If [op] is `null`, this is always `null` as well.
final Interpolation? modifier;

final FileSpan span;

/// Creates an attribute selector that matches any element with a property of
/// the given name.
InterpolatedAttributeSelector(this.name, this.span)
: op = null,
value = null,
modifier = null;

/// Creates an attribute selector that matches an element with a property
/// named [name], whose value matches [value] based on the semantics of [op].
InterpolatedAttributeSelector.withOperator(
this.name,
this.op,
this.value,
this.span, {
this.modifier,
});

/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor) =>
visitor.visitAttributeSelector(this);

String toString() {
var result = '[$name';
if (op != null) {
result += '$op$value';
if (modifier != null) result += ' $modifier';
}
return result;
}
}
32 changes: 32 additions & 0 deletions lib/src/ast/sass/interpolated_selector/class.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../../visitor/interface/interpolated_selector.dart';
import '../../sass/interpolation.dart';
import '../../selector.dart';
import 'simple.dart';

/// A class selector.
///
/// Unlike [ClassSelector], this is parsed during the initial stylesheet parse
/// when `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedClassSelector extends InterpolatedSimpleSelector {
/// The class name this selects for.
final Interpolation name;

FileSpan get span =>
name.span.file.span(name.span.start.offset - 1, name.span.end.offset);

InterpolatedClassSelector(this.name);

/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor) =>
visitor.visitClassSelector(this);

String toString() => '.$name';
}
48 changes: 48 additions & 0 deletions lib/src/ast/sass/interpolated_selector/complex.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../../visitor/interface/interpolated_selector.dart';
import '../../css/value.dart';
import '../../selector.dart';
import '../interpolated_selector.dart';
import 'complex_component.dart';

/// A complex selector before interoplation is resolved.
///
/// Unlike [ComplexSelector], this is parsed during the initial stylesheet parse
/// when `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedComplexSelector extends InterpolatedSelector {
/// This selector's leading combinator.
///
/// If this is null, that indicates that it has no leading combinator. It's only null if
final CssValue<Combinator>? leadingCombinator;

/// The components of this selector.
///
/// This is only empty if [leadingCombinators] is not null.
final List<InterpolatedComplexSelectorComponent> components;

final FileSpan span;

InterpolatedComplexSelector(
Iterable<InterpolatedComplexSelectorComponent> components, this.span,
{this.leadingCombinator})
: components = List.unmodifiable(components) {
if (leadingCombinator == null && this.components.isEmpty) {
throw ArgumentError(
"components may not be empty if leadingCombinator is null.",
);
}
}

/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor) =>
visitor.visitComplexSelector(this);

String toString() => components.join(' ');
}
38 changes: 38 additions & 0 deletions lib/src/ast/sass/interpolated_selector/complex_component.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../css/value.dart';
import '../../selector.dart';
import '../node.dart';
import 'compound.dart';

/// A component of a [InterpolatedComplexSelector].
///
/// Unlike [ComplexSelectorComponent], this is parsed during the initial
/// stylesheet parse when `parseSelectors: true` is passed to
/// [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedComplexSelectorComponent implements SassNode {
/// This component's compound selector.
final InterpolatedCompoundSelector selector;

/// This selector's combinator.
///
/// If this is null, that indicates that it has an implicit descendent
/// combinator.
final CssValue<Combinator>? combinator;

final FileSpan span;

InterpolatedComplexSelectorComponent(this.selector, this.span,
{this.combinator});

String toString() => switch (combinator) {
var combinator? => '$selector $combinator',
_ => selector.toString()
};
}
38 changes: 38 additions & 0 deletions lib/src/ast/sass/interpolated_selector/compound.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../../visitor/interface/interpolated_selector.dart';
import '../../selector.dart';
import '../interpolated_selector.dart';
import 'simple.dart';

/// A compound selector before interoplation is resolved.
///
/// Unlike [CompoundSelector], this is parsed during the initial stylesheet parse
/// when `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedCompoundSelector extends InterpolatedSelector {
/// The components of this selector.
final List<InterpolatedSimpleSelector> components;

FileSpan get span => components.length == 1
? components.first.span
: components.first.span.expand(components.last.span);

InterpolatedCompoundSelector(Iterable<InterpolatedSimpleSelector> components)
: components = List.unmodifiable(components) {
if (this.components.isEmpty) {
throw ArgumentError("components may not be empty.");
}
}

/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor) =>
visitor.visitCompoundSelector(this);

String toString() => components.join('');
}
32 changes: 32 additions & 0 deletions lib/src/ast/sass/interpolated_selector/id.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../../visitor/interface/interpolated_selector.dart';
import '../../sass/interpolation.dart';
import '../../selector.dart';
import 'simple.dart';

/// An ID selector.
///
/// Unlike [IDSelector], this is parsed during the initial stylesheet parse when
/// `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedIDSelector extends InterpolatedSimpleSelector {
/// The id name this selects for.
final Interpolation name;

FileSpan get span =>
name.span.file.span(name.span.start.offset - 1, name.span.end.offset);

InterpolatedIDSelector(this.name);

/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor) =>
visitor.visitIDSelector(this);

String toString() => '#$name';
}
39 changes: 39 additions & 0 deletions lib/src/ast/sass/interpolated_selector/list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../../visitor/interface/interpolated_selector.dart';
import '../interpolated_selector.dart';
import 'complex.dart';

/// A selector list before interoplation is resolved.
///
/// Unlike [SelectorList], this is parsed during the initial stylesheet parse
/// when `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedSelectorList extends InterpolatedSelector {
/// The components of this selector.
///
/// This is never empty.
final List<InterpolatedComplexSelector> components;

FileSpan get span => components.length == 1
? components.first.span
: components.first.span.expand(components.last.span);

InterpolatedSelectorList(Iterable<InterpolatedComplexSelector> components)
: components = List.unmodifiable(components) {
if (this.components.isEmpty) {
throw ArgumentError("components may not be empty.");
}
}

/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor) =>
visitor.visitSelectorList(this);

String toString() => components.join(', ');
}
32 changes: 32 additions & 0 deletions lib/src/ast/sass/interpolated_selector/parent.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:source_span/source_span.dart';

import '../../../visitor/interface/interpolated_selector.dart';
import '../../sass/interpolation.dart';
import '../../selector.dart';
import 'simple.dart';

/// A parent selector.
///
/// Unlike [ParentSelector], this is parsed during the initial stylesheet parse
/// when `parseSelectors: true` is passed to [Stylesheet.parse].
///
/// {@category AST}
final class InterpolatedParentSelector extends InterpolatedSimpleSelector {
/// The suffix that will be added to the parent selector after it's been
/// resolved.
final Interpolation? suffix;

final FileSpan span;

InterpolatedParentSelector(this.span, {this.suffix});

/// Calls the appropriate visit method on [visitor].
T accept<T>(InterpolatedSelectorVisitor<T> visitor) =>
visitor.visitParentSelector(this);

String toString() => switch (suffix) { var suffix? => '&$suffix', _ => '&' };
}
Loading