Skip to content

Commit

Permalink
Add shared interfaces for various AST nodes
Browse files Browse the repository at this point in the history
Fixes #1401 and #1414.

Adds `Dependency`, `SassDeclaration`, and `SassReference` interfaces,
which expose some getters that multiple AST nodes have in common with a
single type.

These also add getters for common subspans (URL, name, and namespace) to
the interfaces.

To allow for `FunctionExpression` to have a common interface with the
other two reference nodes, its old `name` getter has been renamed to
`interpolatedName`, and the new `name` getter now is either just a
string, or null if the name is interpolated.
  • Loading branch information
jathak committed Aug 20, 2021
1 parent fd7eec9 commit a7dad34
Show file tree
Hide file tree
Showing 22 changed files with 208 additions and 40 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.38.1

* No user-visible changes

## 1.38.0

* In expanded mode, emit characters in Unicode private-use areas as escape
Expand Down
3 changes: 3 additions & 0 deletions lib/src/ast/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export 'sass/expression/variable.dart';
export 'sass/import.dart';
export 'sass/import/dynamic.dart';
export 'sass/import/static.dart';
export 'sass/interface/declaration.dart';
export 'sass/interface/dependency.dart';
export 'sass/interface/reference.dart';
export 'sass/interpolation.dart';
export 'sass/node.dart';
export 'sass/statement.dart';
Expand Down
6 changes: 5 additions & 1 deletion lib/src/ast/sass/argument.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import 'package:source_span/source_span.dart';

import '../../utils.dart';
import 'expression.dart';
import 'interface/declaration.dart';
import 'node.dart';

/// An argument declared as part of an [ArgumentDeclaration].
///
/// {@category AST}
@sealed
class Argument implements SassNode {
class Argument implements SassNode, SassDeclaration {
/// The argument name.
final String name;

Expand All @@ -30,6 +31,9 @@ class Argument implements SassNode {
String get originalName =>
defaultValue == null ? span.text : declarationName(span);

FileSpan get nameSpan =>
defaultValue == null ? span : span.subspan(0, name.length + 1);

Argument(this.name, this.span, {this.defaultValue});

String toString() => defaultValue == null ? name : "$name: $defaultValue";
Expand Down
5 changes: 4 additions & 1 deletion lib/src/ast/sass/configured_variable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import 'expression.dart';
import 'interface/declaration.dart';
import 'node.dart';

/// A variable configured by a `with` clause in a `@use` or `@forward` rule.
///
/// {@category AST}
@sealed
class ConfiguredVariable implements SassNode {
class ConfiguredVariable implements SassNode, SassDeclaration {
/// The name of the variable being configured.
final String name;

Expand All @@ -26,6 +27,8 @@ class ConfiguredVariable implements SassNode {

final FileSpan span;

FileSpan get nameSpan => span.subspan(0, name.length + 1);

ConfiguredVariable(this.name, this.expression, this.span,
{bool guarded = false})
: isGuarded = guarded;
Expand Down
20 changes: 16 additions & 4 deletions lib/src/ast/sass/expression/function.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import '../../../visitor/interface/expression.dart';
import '../expression.dart';
import '../argument_invocation.dart';
import '../callable_invocation.dart';
import '../interface/reference.dart';
import '../interpolation.dart';

/// A function invocation.
Expand All @@ -17,7 +18,8 @@ import '../interpolation.dart';
///
/// {@category AST}
@sealed
class FunctionExpression implements Expression, CallableInvocation {
class FunctionExpression
implements Expression, CallableInvocation, SassReference {
/// The namespace of the function being invoked, or `null` if it's invoked
/// without a namespace.
final String? namespace;
Expand All @@ -29,22 +31,32 @@ class FunctionExpression implements Expression, CallableInvocation {
///
/// If this is interpolated, the function will be interpreted as plain CSS,
/// even if it has the same name as a Sass function.
final Interpolation name;
final Interpolation interpolatedName;
String? get name => namespace == null
? interpolatedName.asPlain?.replaceAll('_', '-')
: interpolatedName.asPlain;

/// The arguments to pass to the function.
final ArgumentInvocation arguments;

final FileSpan span;

FunctionExpression(this.name, this.arguments, this.span, {this.namespace});
FileSpan get nameSpan => interpolatedName.span;

FileSpan get namespaceSpan => namespace == null
? span.start.pointSpan()
: span.subspan(0, namespace!.length);

FunctionExpression(this.interpolatedName, this.arguments, this.span,
{this.namespace});

T accept<T>(ExpressionVisitor<T> visitor) =>
visitor.visitFunctionExpression(this);

String toString() {
var buffer = StringBuffer();
if (namespace != null) buffer.write("$namespace.");
buffer.write("$name$arguments");
buffer.write("$interpolatedName$arguments");
return buffer.toString();
}
}
10 changes: 9 additions & 1 deletion lib/src/ast/sass/expression/variable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import 'package:source_span/source_span.dart';

import '../../../visitor/interface/expression.dart';
import '../expression.dart';
import '../interface/reference.dart';

/// A Sass variable.
///
/// {@category AST}
@sealed
class VariableExpression implements Expression {
class VariableExpression implements Expression, SassReference {
/// The namespace of the variable being referenced, or `null` if it's
/// referenced without a namespace.
final String? namespace;
Expand All @@ -22,6 +23,13 @@ class VariableExpression implements Expression {

final FileSpan span;

FileSpan get nameSpan =>
namespace == null ? span : span.subspan(namespace!.length + 1);

FileSpan get namespaceSpan => namespace == null
? span.start.pointSpan()
: span.subspan(0, namespace!.length);

VariableExpression(this.name, this.span, {this.namespace});

T accept<T>(ExpressionVisitor<T> visitor) =>
Expand Down
4 changes: 3 additions & 1 deletion lib/src/ast/sass/import/dynamic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import 'package:source_span/source_span.dart';

import '../expression/string.dart';
import '../import.dart';
import '../interface/dependency.dart';

/// An import that will load a Sass file at runtime.
///
/// {@category AST}
@sealed
class DynamicImport implements Import {
class DynamicImport implements Import, Dependency {
/// The URL of the file to import.
///
/// If this is relative, it's relative to the containing file.
Expand All @@ -30,6 +31,7 @@ class DynamicImport implements Import {
final String urlString;

final FileSpan span;
FileSpan get urlSpan => span;

DynamicImport(this.urlString, this.span);

Expand Down
24 changes: 24 additions & 0 deletions lib/src/ast/sass/interface/declaration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2021 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:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../node.dart';

/// A common interface any node that declares a Sass member.
///
/// {@category AST}
@sealed
abstract class SassDeclaration extends SassNode {
/// The name of the declaration, with underscores converted to hyphens.
///
/// This does not include the `$` for variables.
String get name;

/// The span containing this declaration's name.
///
/// This includes the `$` for variables.
FileSpan get nameSpan;
}
20 changes: 20 additions & 0 deletions lib/src/ast/sass/interface/dependency.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2021 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:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../node.dart';

/// A common interface for [UseRule]s, [ForwardRule]s, and [DynamicImport]s.
///
/// {@category AST}
@sealed
abstract class Dependency extends SassNode {
/// The URL of the dependency this rule loads.
Uri get url;

/// The span of the URL for this dependency, including the quotes.
FileSpan get urlSpan;
}
38 changes: 38 additions & 0 deletions lib/src/ast/sass/interface/reference.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2021 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:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../node.dart';

/// A common interface any node that references a Sass member.
///
/// {@category AST}
@sealed
abstract class SassReference extends SassNode {
/// The namespace of the member being referenced, or `null` if it's referenced
/// without a namespace.
String? get namespace;

/// The name of the member being referenced, with underscores converted to
/// hyphens.
///
/// For [VariableExpression]s and [IncludeRule]s, this will never be null.
/// For [FunctionExpression]s, this will be null if the actual name is an
/// interpolation (in which case this is a plain CSS function, not a reference
/// to a Sass function).
///
/// This does not include the `$` for variables.
String? get name;

/// The span containing this reference's name.
///
/// For variables, this should include the `$`.
FileSpan get nameSpan;

/// The span containing this reference's namespace, or an empty span
/// immediately before the name if the namespace is null.
FileSpan get namespaceSpan;
}
6 changes: 5 additions & 1 deletion lib/src/ast/sass/statement/forward_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../configured_variable.dart';
import '../expression/string.dart';
import '../interface/dependency.dart';
import '../statement.dart';

/// A `@forward` rule.
///
/// {@category AST}
@sealed
class ForwardRule implements Statement {
class ForwardRule implements Statement, Dependency {
/// The URI of the module to forward.
///
/// If this is relative, it's relative to the containing file.
Expand Down Expand Up @@ -74,6 +75,9 @@ class ForwardRule implements Statement {

final FileSpan span;

FileSpan get urlSpan =>
span.subspan(RegExp(r'@forward\s+').matchAsPrefix(span.text)!.end);

/// Creates a `@forward` rule that allows all members to be accessed.
ForwardRule(this.url, this.span,
{this.prefix, Iterable<ConfiguredVariable>? configuration})
Expand Down
9 changes: 8 additions & 1 deletion lib/src/ast/sass/statement/function_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:source_span/source_span.dart';

import '../../../visitor/interface/statement.dart';
import '../argument_declaration.dart';
import '../interface/declaration.dart';
import '../statement.dart';
import 'callable_declaration.dart';
import 'silent_comment.dart';
Expand All @@ -17,7 +18,13 @@ import 'silent_comment.dart';
///
/// {@category AST}
@sealed
class FunctionRule extends CallableDeclaration {
class FunctionRule extends CallableDeclaration implements SassDeclaration {
FileSpan get nameSpan {
var match = RegExp(r'@function\s*').matchAsPrefix(span.text);
var start = match!.end;
return span.subspan(start, start + name.length);
}

FunctionRule(String name, ArgumentDeclaration arguments,
Iterable<Statement> children, FileSpan span,
{SilentComment? comment})
Expand Down
16 changes: 15 additions & 1 deletion lib/src/ast/sass/statement/include_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import '../../../utils.dart';
import '../../../visitor/interface/statement.dart';
import '../argument_invocation.dart';
import '../callable_invocation.dart';
import '../interface/reference.dart';
import '../statement.dart';
import 'content_block.dart';

/// A mixin invocation.
///
/// {@category AST}
@sealed
class IncludeRule implements Statement, CallableInvocation {
class IncludeRule implements Statement, CallableInvocation, SassReference {
/// The namespace of the mixin being invoked, or `null` if it's invoked
/// without a namespace.
final String? namespace;
Expand All @@ -39,6 +40,19 @@ class IncludeRule implements Statement, CallableInvocation {
? span
: span.file.span(span.start.offset, arguments.span.end.offset).trim();

FileSpan get nameSpan {
var match = RegExp(r'(\+|@include)\s*').matchAsPrefix(span.text);
var start = match!.end;
if (namespace != null) start += namespace!.length + 1;
return span.subspan(start, start + name.length);
}

FileSpan get namespaceSpan {
var match = RegExp(r'(\+|@include)\s*').matchAsPrefix(span.text);
var start = match!.end;
return span.subspan(start, start + (namespace?.length ?? 0));
}

IncludeRule(this.name, this.arguments, this.span,
{this.namespace, this.content});

Expand Down
9 changes: 8 additions & 1 deletion lib/src/ast/sass/statement/mixin_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../../../visitor/statement_search.dart';
import '../argument_declaration.dart';
import '../interface/declaration.dart';
import '../statement.dart';
import 'callable_declaration.dart';
import 'content_rule.dart';
Expand All @@ -19,11 +20,17 @@ import 'silent_comment.dart';
///
/// {@category AST}
@sealed
class MixinRule extends CallableDeclaration {
class MixinRule extends CallableDeclaration implements SassDeclaration {
/// Whether the mixin contains a `@content` rule.
late final bool hasContent =
const _HasContentVisitor().visitMixinRule(this) == true;

FileSpan get nameSpan {
var match = RegExp(r'(=|@mixin)\s*').matchAsPrefix(span.text);
var start = match!.end;
return span.subspan(start, start + name.length);
}

MixinRule(String name, ArgumentDeclaration arguments,
Iterable<Statement> children, FileSpan span,
{SilentComment? comment})
Expand Down
6 changes: 5 additions & 1 deletion lib/src/ast/sass/statement/use_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import '../../../parse/scss.dart';
import '../../../visitor/interface/statement.dart';
import '../configured_variable.dart';
import '../expression/string.dart';
import '../interface/dependency.dart';
import '../statement.dart';

/// A `@use` rule.
///
/// {@category AST}
@sealed
class UseRule implements Statement {
class UseRule implements Statement, Dependency {
/// The URI of the module to use.
///
/// If this is relative, it's relative to the containing file.
Expand All @@ -32,6 +33,9 @@ class UseRule implements Statement {

final FileSpan span;

FileSpan get urlSpan =>
span.subspan(RegExp(r'@use\s+').matchAsPrefix(span.text)!.end);

UseRule(this.url, this.namespace, this.span,
{Iterable<ConfiguredVariable>? configuration})
: configuration = configuration == null
Expand Down
Loading

0 comments on commit a7dad34

Please sign in to comment.