Skip to content

Commit

Permalink
Add shared interfaces for various AST nodes (#1445)
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.
  • Loading branch information
jathak committed Aug 23, 2021
1 parent fd7eec9 commit d419df7
Show file tree
Hide file tree
Showing 30 changed files with 425 additions and 119 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
4 changes: 4 additions & 0 deletions lib/src/ast/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ export 'sass/argument_invocation.dart';
export 'sass/at_root_query.dart';
export 'sass/callable_invocation.dart';
export 'sass/configured_variable.dart';
export 'sass/declaration.dart';
export 'sass/dependency.dart';
export 'sass/expression.dart';
export 'sass/expression/binary_operation.dart';
export 'sass/expression/boolean.dart';
export 'sass/expression/color.dart';
export 'sass/expression/function.dart';
export 'sass/expression/if.dart';
export 'sass/expression/interpolated_function.dart';
export 'sass/expression/list.dart';
export 'sass/expression/map.dart';
export 'sass/expression/null.dart';
Expand All @@ -29,6 +32,7 @@ export 'sass/import/dynamic.dart';
export 'sass/import/static.dart';
export 'sass/interpolation.dart';
export 'sass/node.dart';
export 'sass/reference.dart';
export 'sass/statement.dart';
export 'sass/statement/at_root_rule.dart';
export 'sass/statement/at_rule.dart';
Expand Down
7 changes: 6 additions & 1 deletion lib/src/ast/sass/argument.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../utils.dart';
import '../../util/span.dart';
import 'expression.dart';
import '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 +32,9 @@ class Argument implements SassNode {
String get originalName =>
defaultValue == null ? span.text : declarationName(span);

FileSpan get nameSpan =>
defaultValue == null ? span : span.initialIdentifier(includeLeading: 1);

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

String toString() => defaultValue == null ? name : "$name: $defaultValue";
Expand Down
1 change: 1 addition & 0 deletions lib/src/ast/sass/argument_declaration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../logger.dart';
import '../../parse/scss.dart';
import '../../utils.dart';
import '../../util/character.dart';
import '../../util/span.dart';
import 'argument.dart';
import 'node.dart';

Expand Down
6 changes: 5 additions & 1 deletion lib/src/ast/sass/configured_variable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../util/span.dart';
import 'expression.dart';
import '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 +28,8 @@ class ConfiguredVariable implements SassNode {

final FileSpan span;

FileSpan get nameSpan => span.initialIdentifier(includeLeading: 1);

ConfiguredVariable(this.name, this.expression, this.span,
{bool guarded = false})
: isGuarded = guarded;
Expand Down
24 changes: 24 additions & 0 deletions lib/src/ast/sass/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 for 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/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 SassDependency 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: 25 additions & 13 deletions lib/src/ast/sass/expression/function.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,58 @@
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../../util/span.dart';
import '../../../visitor/interface/expression.dart';
import '../expression.dart';
import '../argument_invocation.dart';
import '../callable_invocation.dart';
import '../interpolation.dart';
import '../reference.dart';

/// A function invocation.
///
/// This may be a plain CSS function or a Sass function.
/// This may be a plain CSS function or a Sass function, but may not include
/// interpolation.
///
/// {@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;

/// The name of the function being invoked.
///
/// If [namespace] is non-`null`, underscores are converted to hyphens in this name.
/// If [namespace] is `null`, this could be a plain CSS function call, so underscores are kept unchanged.
///
/// 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;
/// The name of the function being invoked, with underscores left as-is.
final String originalName;

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

final FileSpan span;

FunctionExpression(this.name, this.arguments, this.span, {this.namespace});
/// The name of the function being invoked, with underscores converted to
/// hyphens.
///
/// If this function is a plain CSS function, use [originalName] instead.
String get name => originalName.replaceAll('_', '-');

FileSpan get nameSpan {
if (namespace == null) return span.initialIdentifier();
return span.withoutNamespace().initialIdentifier();
}

FileSpan? get namespaceSpan =>
namespace == null ? null : span.initialIdentifier();

FunctionExpression(this.originalName, 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("$originalName$arguments");
return buffer.toString();
}
}
35 changes: 35 additions & 0 deletions lib/src/ast/sass/expression/interpolated_function.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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 '../../../visitor/interface/expression.dart';
import '../expression.dart';
import '../argument_invocation.dart';
import '../callable_invocation.dart';
import '../interpolation.dart';

/// An interpolated function invocation.
///
/// This is always a plain CSS function.
///
/// {@category AST}
@sealed
class InterpolatedFunctionExpression implements Expression, CallableInvocation {
/// The name of the function being invoked.
final Interpolation name;

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

final FileSpan span;

InterpolatedFunctionExpression(this.name, this.arguments, this.span);

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

String toString() => '$name$arguments';
}
12 changes: 11 additions & 1 deletion lib/src/ast/sass/expression/variable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../../util/span.dart';
import '../../../visitor/interface/expression.dart';
import '../expression.dart';
import '../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 +24,14 @@ class VariableExpression implements Expression {

final FileSpan span;

FileSpan get nameSpan {
if (namespace == null) return span;
return span.withoutNamespace();
}

FileSpan? get namespaceSpan =>
namespace == null ? null : span.initialIdentifier();

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 @@ -6,13 +6,14 @@ import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

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

/// An import that will load a Sass file at runtime.
///
/// {@category AST}
@sealed
class DynamicImport implements Import {
class DynamicImport implements Import, SassDependency {
/// 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
33 changes: 33 additions & 0 deletions lib/src/ast/sass/reference.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 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.
///
/// 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, null if [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 @@ -6,16 +6,18 @@ import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';

import '../../../util/span.dart';
import '../../../visitor/interface/statement.dart';
import '../configured_variable.dart';
import '../dependency.dart';
import '../expression/string.dart';
import '../statement.dart';

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

final FileSpan span;

FileSpan get urlSpan => span.withoutInitialAtRule().initialQuoted();

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

import '../../../util/span.dart';
import '../../../visitor/interface/statement.dart';
import '../argument_declaration.dart';
import '../declaration.dart';
import '../statement.dart';
import 'callable_declaration.dart';
import 'silent_comment.dart';
Expand All @@ -17,7 +19,9 @@ import 'silent_comment.dart';
///
/// {@category AST}
@sealed
class FunctionRule extends CallableDeclaration {
class FunctionRule extends CallableDeclaration implements SassDeclaration {
FileSpan get nameSpan => span.withoutInitialAtRule().initialIdentifier();

FunctionRule(String name, ArgumentDeclaration arguments,
Iterable<Statement> children, FileSpan span,
{SilentComment? comment})
Expand Down
Loading

0 comments on commit d419df7

Please sign in to comment.