Skip to content

Commit

Permalink
Track original source spans for selectors (#1903)
Browse files Browse the repository at this point in the history
Closes #1783
  • Loading branch information
nex3 authored Mar 8, 2023
1 parent 434f2b9 commit 9417b6e
Show file tree
Hide file tree
Showing 49 changed files with 1,187 additions and 756 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

* Improve error messages for invalid CSS values passed to plain CSS functions.

* Improve error messages involving selectors.

### Embedded Sass

* Improve the performance of starting up a compilation.
Expand Down
7 changes: 5 additions & 2 deletions lib/src/ast/css/media_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import '../../interpolation_map.dart';
import '../../logger.dart';
import '../../parse/media_query.dart';
import '../../utils.dart';
Expand Down Expand Up @@ -43,8 +44,10 @@ class CssMediaQuery {
///
/// Throws a [SassFormatException] if parsing fails.
static List<CssMediaQuery> parseList(String contents,
{Object? url, Logger? logger}) =>
MediaQueryParser(contents, url: url, logger: logger).parse();
{Object? url, Logger? logger, InterpolationMap? interpolationMap}) =>
MediaQueryParser(contents,
url: url, logger: logger, interpolationMap: interpolationMap)
.parse();

/// Creates a media query specifies a type and, optionally, conditions.
///
Expand Down
1 change: 0 additions & 1 deletion lib/src/ast/css/modifiable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ export 'modifiable/node.dart';
export 'modifiable/style_rule.dart';
export 'modifiable/stylesheet.dart';
export 'modifiable/supports_rule.dart';
export 'modifiable/value.dart';
17 changes: 11 additions & 6 deletions lib/src/ast/css/modifiable/style_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,35 @@

import 'package:source_span/source_span.dart';

import '../../../util/box.dart';
import '../../../visitor/interface/modifiable_css.dart';
import '../../selector.dart';
import '../style_rule.dart';
import 'node.dart';
import 'value.dart';

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

/// A reference to the modifiable selector list provided by the extension
/// store, which may update it over time as new extensions are applied.
final Box<SelectorList> _selector;

final SelectorList originalSelector;
final FileSpan span;

/// Creates a new [ModifiableCssStyleRule].
///
/// If [originalSelector] isn't passed, it defaults to [selector.value].
ModifiableCssStyleRule(this.selector, this.span,
/// If [originalSelector] isn't passed, it defaults to [_selector.value].
ModifiableCssStyleRule(this._selector, this.span,
{SelectorList? originalSelector})
: originalSelector = originalSelector ?? selector.value;
: originalSelector = originalSelector ?? _selector.value;

T accept<T>(ModifiableCssVisitor<T> visitor) =>
visitor.visitCssStyleRule(this);

ModifiableCssStyleRule copyWithoutChildren() =>
ModifiableCssStyleRule(selector, span,
ModifiableCssStyleRule(_selector, span,
originalSelector: originalSelector);
}
17 changes: 0 additions & 17 deletions lib/src/ast/css/modifiable/value.dart

This file was deleted.

4 changes: 2 additions & 2 deletions lib/src/ast/css/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class _IsInvisibleVisitor with EveryCssVisitor {

bool visitCssStyleRule(CssStyleRule rule) =>
(includeBogus
? rule.selector.value.isInvisible
: rule.selector.value.isInvisibleOtherThanBogusCombinators) ||
? rule.selector.isInvisible
: rule.selector.isInvisibleOtherThanBogusCombinators) ||
super.visitCssStyleRule(rule);
}
3 changes: 1 addition & 2 deletions lib/src/ast/css/style_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import '../../visitor/interface/css.dart';
import '../selector.dart';
import 'node.dart';
import 'value.dart';

/// A plain CSS style rule.
///
Expand All @@ -14,7 +13,7 @@ import 'value.dart';
/// contain placeholder selectors.
abstract class CssStyleRule extends CssParentNode {
/// The selector for this rule.
CssValue<SelectorList> get selector;
SelectorList get selector;

/// The selector for this rule, before any extensions were applied.
SelectorList get originalSelector;
Expand Down
7 changes: 6 additions & 1 deletion lib/src/ast/css/value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import '../node.dart';
/// A value in a plain CSS tree.
///
/// This is used to associate a span with a value that doesn't otherwise track
/// its span.
/// its span. It has value equality semantics.
class CssValue<T extends Object> implements AstNode {
/// The value.
final T value;
Expand All @@ -19,5 +19,10 @@ class CssValue<T extends Object> implements AstNode {

CssValue(this.value, this.span);

bool operator ==(Object other) =>
other is CssValue<T> && other.value == value;

int get hashCode => value.hashCode;

String toString() => value.toString();
}
7 changes: 6 additions & 1 deletion lib/src/ast/sass/at_root_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:meta/meta.dart';
import 'package:collection/collection.dart';

import '../../exception.dart';
import '../../interpolation_map.dart';
import '../../logger.dart';
import '../../parse/at_root_query.dart';
import '../css.dart';
Expand Down Expand Up @@ -53,8 +54,12 @@ class AtRootQuery {
///
/// If passed, [url] is the name of the file from which [contents] comes.
///
/// If passed, [interpolationMap] maps the text of [contents] back to the
/// original location of the selector in the source file.
///
/// Throws a [SassFormatException] if parsing fails.
factory AtRootQuery.parse(String contents, {Object? url, Logger? logger}) =>
factory AtRootQuery.parse(String contents,
{Object? url, Logger? logger, InterpolationMap? interpolationMap}) =>
AtRootQueryParser(contents, url: url, logger: logger).parse();

/// Returns whether [this] excludes [node].
Expand Down
10 changes: 8 additions & 2 deletions lib/src/ast/selector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../evaluation_context.dart';
import '../exception.dart';
import '../visitor/any_selector.dart';
import '../visitor/interface/selector.dart';
import '../visitor/serialize.dart';
import 'node.dart';
import 'selector/complex.dart';
import 'selector/list.dart';
import 'selector/placeholder.dart';
Expand Down Expand Up @@ -38,7 +40,7 @@ export 'selector/universal.dart';
/// Selectors have structural equality semantics.
///
/// {@category AST}
abstract class Selector {
abstract class Selector implements AstNode {
/// Whether this selector, and complex selectors containing it, should not be
/// emitted.
///
Expand Down Expand Up @@ -76,10 +78,14 @@ abstract class Selector {
@internal
bool get isUseless => accept(const _IsUselessVisitor());

final FileSpan span;

Selector(this.span);

/// Prints a warning if [this] is a bogus selector.
///
/// This may only be called from within a custom Sass function. This will
/// throw a [SassScriptException] in Dart Sass 2.0.0.
/// throw a [SassException] in Dart Sass 2.0.0.
void assertNotBogus({String? name}) {
if (!isBogus) return;
warn(
Expand Down
11 changes: 7 additions & 4 deletions lib/src/ast/selector/attribute.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../visitor/interface/selector.dart';
import '../selector.dart';
Expand Down Expand Up @@ -44,15 +45,17 @@ class AttributeSelector extends SimpleSelector {

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

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

T accept<T>(SelectorVisitor<T> visitor) =>
visitor.visitAttributeSelector(this);
Expand Down
5 changes: 3 additions & 2 deletions lib/src/ast/selector/class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../visitor/interface/selector.dart';
import '../selector.dart';
Expand All @@ -18,7 +19,7 @@ class ClassSelector extends SimpleSelector {
/// The class name this selects for.
final String name;

ClassSelector(this.name);
ClassSelector(this.name, FileSpan span) : super(span);

bool operator ==(Object other) =>
other is ClassSelector && other.name == name;
Expand All @@ -27,7 +28,7 @@ class ClassSelector extends SimpleSelector {

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

int get hashCode => name.hashCode;
}
33 changes: 23 additions & 10 deletions lib/src/ast/selector/complex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../extend/functions.dart';
import '../../logger.dart';
import '../../parse/selector.dart';
import '../../utils.dart';
import '../../visitor/interface/selector.dart';
import '../css/value.dart';
import '../selector.dart';

/// A complex selector.
Expand All @@ -25,7 +27,7 @@ class ComplexSelector extends Selector {
/// If this is empty, that indicates that it has no leading combinator. If
/// it's more than one element, that means it's invalid CSS; however, we still
/// support this for backwards-compatibility purposes.
final List<Combinator> leadingCombinators;
final List<CssValue<Combinator>> leadingCombinators;

/// The components of this selector.
///
Expand Down Expand Up @@ -66,11 +68,12 @@ class ComplexSelector extends Selector {
? components.first.selector
: null;

ComplexSelector(Iterable<Combinator> leadingCombinators,
Iterable<ComplexSelectorComponent> components,
ComplexSelector(Iterable<CssValue<Combinator>> leadingCombinators,
Iterable<ComplexSelectorComponent> components, FileSpan span,
{this.lineBreak = false})
: leadingCombinators = List.unmodifiable(leadingCombinators),
components = List.unmodifiable(components) {
components = List.unmodifiable(components),
super(span) {
if (this.leadingCombinators.isEmpty && this.components.isEmpty) {
throw ArgumentError(
"leadingCombinators and components may not both be empty.");
Expand Down Expand Up @@ -109,12 +112,14 @@ class ComplexSelector extends Selector {
///
/// @nodoc
@internal
ComplexSelector withAdditionalCombinators(List<Combinator> combinators,
ComplexSelector withAdditionalCombinators(
List<CssValue<Combinator>> combinators,
{bool forceLineBreak = false}) {
if (combinators.isEmpty) {
return this;
} else if (components.isEmpty) {
return ComplexSelector([...leadingCombinators, ...combinators], const [],
return ComplexSelector(
[...leadingCombinators, ...combinators], const [], span,
lineBreak: lineBreak || forceLineBreak);
} else {
return ComplexSelector(
Expand All @@ -123,6 +128,7 @@ class ComplexSelector extends Selector {
...components.exceptLast,
components.last.withAdditionalCombinators(combinators)
],
span,
lineBreak: lineBreak || forceLineBreak);
}
}
Expand All @@ -132,33 +138,39 @@ class ComplexSelector extends Selector {
/// If [forceLineBreak] is `true`, this will mark the new complex selector as
/// having a line break.
///
/// The [span] is used for the new selector.
///
/// @nodoc
@internal
ComplexSelector withAdditionalComponent(ComplexSelectorComponent component,
ComplexSelector withAdditionalComponent(
ComplexSelectorComponent component, FileSpan span,
{bool forceLineBreak = false}) =>
ComplexSelector(leadingCombinators, [...components, component],
ComplexSelector(leadingCombinators, [...components, component], span,
lineBreak: lineBreak || forceLineBreak);

/// Returns a copy of `this` with [child]'s combinators added to the end.
///
/// If [child] has [leadingCombinators], they're appended to `this`'s last
/// combinator. This does _not_ resolve parent selectors.
///
/// The [span] is used for the new selector.
///
/// If [forceLineBreak] is `true`, this will mark the new complex selector as
/// having a line break.
///
/// @nodoc
@internal
ComplexSelector concatenate(ComplexSelector child,
ComplexSelector concatenate(ComplexSelector child, FileSpan span,
{bool forceLineBreak = false}) {
if (child.leadingCombinators.isEmpty) {
return ComplexSelector(
leadingCombinators, [...components, ...child.components],
leadingCombinators, [...components, ...child.components], span,
lineBreak: lineBreak || child.lineBreak || forceLineBreak);
} else if (components.isEmpty) {
return ComplexSelector(
[...leadingCombinators, ...child.leadingCombinators],
child.components,
span,
lineBreak: lineBreak || child.lineBreak || forceLineBreak);
} else {
return ComplexSelector(
Expand All @@ -168,6 +180,7 @@ class ComplexSelector extends Selector {
components.last.withAdditionalCombinators(child.leadingCombinators),
...child.components
],
span,
lineBreak: lineBreak || child.lineBreak || forceLineBreak);
}
}
Expand Down
Loading

0 comments on commit 9417b6e

Please sign in to comment.