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

Track original source spans for selectors #1903

Merged
merged 4 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
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