Skip to content

Commit

Permalink
Expose selector APIs through sass_api (#1741)
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Jul 15, 2022
1 parent f611b06 commit fd4c50c
Show file tree
Hide file tree
Showing 22 changed files with 310 additions and 82 deletions.
2 changes: 1 addition & 1 deletion lib/sass.dart
Expand Up @@ -26,7 +26,7 @@ export 'src/exception.dart' show SassException;
export 'src/importer.dart';
export 'src/logger.dart';
export 'src/syntax.dart';
export 'src/value.dart' hide ColorFormat, SassApiColor, SpanColorFormat;
export 'src/value.dart' hide ColorFormat, SassApiColor, SassApiValue, SpanColorFormat;
export 'src/visitor/serialize.dart' show OutputStyle;
export 'src/evaluation_context.dart' show warn;

Expand Down
5 changes: 5 additions & 0 deletions lib/src/ast/selector.dart
Expand Up @@ -2,6 +2,8 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

import '../visitor/interface/selector.dart';
import '../visitor/serialize.dart';

Expand All @@ -25,9 +27,12 @@ export 'selector/universal.dart';
/// [ParentSelector] or a [PlaceholderSelector].
///
/// Selectors have structural equality semantics.
///
/// {@category Selector}
abstract class Selector {
/// Whether this selector, and complex selectors containing it, should not be
/// emitted.
@internal
bool get isInvisible => false;

/// Calls the appropriate visit method on [visitor].
Expand Down
5 changes: 5 additions & 0 deletions lib/src/ast/selector/attribute.dart
Expand Up @@ -2,13 +2,18 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

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

/// An attribute selector.
///
/// This selects for elements with the given attribute, and optionally with a
/// value matching certain conditions as well.
///
/// {@category Selector}
@sealed
class AttributeSelector extends SimpleSelector {
/// The name of the attribute being selected for.
final QualifiedName name;
Expand Down
7 changes: 7 additions & 0 deletions lib/src/ast/selector/class.dart
Expand Up @@ -2,13 +2,18 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

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

/// A class selector.
///
/// This selects elements whose `class` attribute contains an identifier with
/// the given name.
///
/// {@category Selector}
@sealed
class ClassSelector extends SimpleSelector {
/// The class name this selects for.
final String name;
Expand All @@ -20,6 +25,8 @@ class ClassSelector extends SimpleSelector {

T accept<T>(SelectorVisitor<T> visitor) => visitor.visitClassSelector(this);

/// @nodoc
@internal
ClassSelector addSuffix(String suffix) => ClassSelector(name + suffix);

int get hashCode => name.hashCode;
Expand Down
30 changes: 30 additions & 0 deletions lib/src/ast/selector/complex.dart
Expand Up @@ -2,7 +2,11 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

import '../../extend/functions.dart';
import '../../logger.dart';
import '../../parse/selector.dart';
import '../../utils.dart';
import '../../visitor/interface/selector.dart';
import '../selector.dart';
Expand All @@ -11,6 +15,9 @@ import '../selector.dart';
///
/// A complex selector is composed of [CompoundSelector]s separated by
/// [Combinator]s. It selects elements based on their parent selectors.
///
/// {@category Selector}
@sealed
class ComplexSelector extends Selector {
/// The components of this selector.
///
Expand All @@ -25,6 +32,9 @@ class ComplexSelector extends Selector {
final List<ComplexSelectorComponent> components;

/// Whether a line break should be emitted *before* this selector.
///
/// @nodoc
@internal
final bool lineBreak;

/// The minimum possible specificity that this selector can have.
Expand All @@ -49,6 +59,8 @@ class ComplexSelector extends Selector {

int? _maxSpecificity;

/// @nodoc
@internal
late final bool isInvisible = components.any(
(component) => component is CompoundSelector && component.isInvisible);

Expand All @@ -60,6 +72,19 @@ class ComplexSelector extends Selector {
}
}

/// Parses a complex selector from [contents].
///
/// If passed, [url] is the name of the file from which [contents] comes.
/// [allowParent] controls whether a [ParentSelector] is allowed in this
/// selector.
///
/// Throws a [SassFormatException] if parsing fails.
factory ComplexSelector.parse(String contents,
{Object? url, Logger? logger, bool allowParent = true}) =>
SelectorParser(contents,
url: url, logger: logger, allowParent: allowParent)
.parseComplexSelector();

T accept<T>(SelectorVisitor<T> visitor) => visitor.visitComplexSelector(this);

/// Whether this is a superselector of [other].
Expand Down Expand Up @@ -92,10 +117,15 @@ class ComplexSelector extends Selector {
/// A component of a [ComplexSelector].
///
/// This is either a [CompoundSelector] or a [Combinator].
///
/// {@category Selector}
abstract class ComplexSelectorComponent {}

/// A combinator that defines the relationship between selectors in a
/// [ComplexSelector].
///
/// {@category Selector}
@sealed
class Combinator implements ComplexSelectorComponent {
/// Matches the right-hand selector if it's immediately adjacent to the
/// left-hand selector in the DOM tree.
Expand Down
7 changes: 7 additions & 0 deletions lib/src/ast/selector/compound.dart
Expand Up @@ -2,6 +2,8 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

import '../../extend/functions.dart';
import '../../logger.dart';
import '../../parse/selector.dart';
Expand All @@ -13,6 +15,9 @@ import '../selector.dart';
///
/// A compound selector is composed of [SimpleSelector]s. It matches an element
/// that matches all of the component simple selectors.
///
/// {@category Selector}
@sealed
class CompoundSelector extends Selector implements ComplexSelectorComponent {
/// The components of this selector.
///
Expand Down Expand Up @@ -41,6 +46,8 @@ class CompoundSelector extends Selector implements ComplexSelectorComponent {

int? _maxSpecificity;

/// @nodoc
@internal
bool get isInvisible => components.any((component) => component.isInvisible);

CompoundSelector(Iterable<SimpleSelector> components)
Expand Down
9 changes: 9 additions & 0 deletions lib/src/ast/selector/id.dart
Expand Up @@ -4,12 +4,17 @@

import 'dart:math' as math;

import 'package:meta/meta.dart';

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

/// An ID selector.
///
/// This selects elements whose `id` attribute exactly matches the given name.
///
/// {@category Selector}
@sealed
class IDSelector extends SimpleSelector {
/// The ID name this selects for.
final String name;
Expand All @@ -20,8 +25,12 @@ class IDSelector extends SimpleSelector {

T accept<T>(SelectorVisitor<T> visitor) => visitor.visitIDSelector(this);

/// @nodoc
@internal
IDSelector addSuffix(String suffix) => IDSelector(name + suffix);

/// @nodoc
@internal
List<SimpleSelector>? unify(List<SimpleSelector> compound) {
// A given compound selector may only contain one ID.
if (compound.any((simple) => simple is IDSelector && simple != this)) {
Expand Down
9 changes: 8 additions & 1 deletion lib/src/ast/selector/list.dart
Expand Up @@ -2,6 +2,8 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

import '../../extend/functions.dart';
import '../../logger.dart';
import '../../parse/selector.dart';
Expand All @@ -13,8 +15,11 @@ import '../selector.dart';

/// A selector list.
///
/// A selector list is composed of [ComplexSelector]s. It matches an element
/// A selector list is composed of [ComplexSelector]s. It matches any element
/// that matches any of the component selectors.
///
/// {@category Selector}
@sealed
class SelectorList extends Selector {
/// The components of this selector.
///
Expand All @@ -25,6 +30,8 @@ class SelectorList extends Selector {
bool get _containsParentSelector =>
components.any(_complexContainsParentSelector);

/// @nodoc
@internal
bool get isInvisible => components.every((complex) => complex.isInvisible);

/// Returns a SassScript list that represents this selector.
Expand Down
7 changes: 7 additions & 0 deletions lib/src/ast/selector/parent.dart
Expand Up @@ -2,13 +2,18 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

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

/// A selector that matches the parent in the Sass stylesheet.
///
/// This is not a plain CSS selector—it should be removed before emitting a CSS
/// document.
///
/// {@category Selector}
@sealed
class ParentSelector extends SimpleSelector {
/// The suffix that will be added to the parent selector after it's been
/// resolved.
Expand All @@ -21,6 +26,8 @@ class ParentSelector extends SimpleSelector {

T accept<T>(SelectorVisitor<T> visitor) => visitor.visitParentSelector(this);

/// @nodoc
@internal
List<SimpleSelector> unify(List<SimpleSelector> compound) =>
throw UnsupportedError("& doesn't support unification.");
}
9 changes: 9 additions & 0 deletions lib/src/ast/selector/placeholder.dart
Expand Up @@ -2,6 +2,8 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

import '../../util/character.dart' as character;
import '../../visitor/interface/selector.dart';
import '../selector.dart';
Expand All @@ -11,10 +13,15 @@ import '../selector.dart';
/// This doesn't match any elements. It's intended to be extended using
/// `@extend`. It's not a plain CSS selector—it should be removed before
/// emitting a CSS document.
///
/// {@category Selector}
@sealed
class PlaceholderSelector extends SimpleSelector {
/// The name of the placeholder.
final String name;

/// @nodoc
@internal
bool get isInvisible => true;

/// Returns whether this is a private selector (that is, whether it begins
Expand All @@ -26,6 +33,8 @@ class PlaceholderSelector extends SimpleSelector {
T accept<T>(SelectorVisitor<T> visitor) =>
visitor.visitPlaceholderSelector(this);

/// @nodoc
@internal
PlaceholderSelector addSuffix(String suffix) =>
PlaceholderSelector(name + suffix);

Expand Down
16 changes: 16 additions & 0 deletions lib/src/ast/selector/pseudo.dart
Expand Up @@ -17,11 +17,17 @@ import '../selector.dart';
/// selectors take arguments, including other selectors. Sass manually encodes
/// logic for each pseudo selector that takes a selector as an argument, to
/// ensure that extension and other selector operations work properly.
///
/// {@category Selector}
@sealed
class PseudoSelector extends SimpleSelector {
/// The name of this selector.
final String name;

/// Like [name], but without any vendor prefixes.
///
/// @nodoc
@internal
final String normalizedName;

/// Whether this is a pseudo-class selector.
Expand Down Expand Up @@ -49,10 +55,14 @@ class PseudoSelector extends SimpleSelector {
bool get isSyntacticElement => !isSyntacticClass;

/// Whether this is a valid `:host` selector.
///
/// @nodoc
@internal
bool get isHost => isClass && name == 'host';

/// Whether this is a valid `:host-context` selector.
///
/// @nodoc
@internal
bool get isHostContext =>
isClass && name == 'host-context' && selector != null;
Expand Down Expand Up @@ -83,6 +93,8 @@ class PseudoSelector extends SimpleSelector {

int? _maxSpecificity;

/// @nodoc
@internal
bool get isInvisible {
var selector = this.selector;
if (selector == null) return false;
Expand Down Expand Up @@ -128,11 +140,15 @@ class PseudoSelector extends SimpleSelector {
PseudoSelector withSelector(SelectorList selector) => PseudoSelector(name,
element: isElement, argument: argument, selector: selector);

/// @nodoc
@internal
PseudoSelector addSuffix(String suffix) {
if (argument != null || selector != null) super.addSuffix(suffix);
return PseudoSelector(name + suffix, element: isElement);
}

/// @nodoc
@internal
List<SimpleSelector>? unify(List<SimpleSelector> compound) {
if (name == 'host' || name == 'host-context') {
if (!compound.every((simple) =>
Expand Down
7 changes: 6 additions & 1 deletion lib/src/ast/selector/qualified_name.dart
Expand Up @@ -2,9 +2,14 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

/// A [qualified name][].
import 'package:meta/meta.dart';

/// A [qualified name].
///
/// [qualified name]: https://www.w3.org/TR/css3-namespace/#css-qnames
///
/// {@category Selector}
@sealed
class QualifiedName {
/// The identifier name.
final String name;
Expand Down

0 comments on commit fd4c50c

Please sign in to comment.