Skip to content

Commit d419df7

Browse files
authored
Add shared interfaces for various AST nodes (#1445)
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.
1 parent fd7eec9 commit d419df7

30 files changed

+425
-119
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.38.1
2+
3+
* No user-visible changes
4+
15
## 1.38.0
26

37
* In expanded mode, emit characters in Unicode private-use areas as escape

lib/src/ast/sass.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ export 'sass/argument_invocation.dart';
88
export 'sass/at_root_query.dart';
99
export 'sass/callable_invocation.dart';
1010
export 'sass/configured_variable.dart';
11+
export 'sass/declaration.dart';
12+
export 'sass/dependency.dart';
1113
export 'sass/expression.dart';
1214
export 'sass/expression/binary_operation.dart';
1315
export 'sass/expression/boolean.dart';
1416
export 'sass/expression/color.dart';
1517
export 'sass/expression/function.dart';
1618
export 'sass/expression/if.dart';
19+
export 'sass/expression/interpolated_function.dart';
1720
export 'sass/expression/list.dart';
1821
export 'sass/expression/map.dart';
1922
export 'sass/expression/null.dart';
@@ -29,6 +32,7 @@ export 'sass/import/dynamic.dart';
2932
export 'sass/import/static.dart';
3033
export 'sass/interpolation.dart';
3134
export 'sass/node.dart';
35+
export 'sass/reference.dart';
3236
export 'sass/statement.dart';
3337
export 'sass/statement/at_root_rule.dart';
3438
export 'sass/statement/at_rule.dart';

lib/src/ast/sass/argument.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import 'package:meta/meta.dart';
66
import 'package:source_span/source_span.dart';
77

88
import '../../utils.dart';
9+
import '../../util/span.dart';
910
import 'expression.dart';
11+
import 'declaration.dart';
1012
import 'node.dart';
1113

1214
/// An argument declared as part of an [ArgumentDeclaration].
1315
///
1416
/// {@category AST}
1517
@sealed
16-
class Argument implements SassNode {
18+
class Argument implements SassNode, SassDeclaration {
1719
/// The argument name.
1820
final String name;
1921

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

35+
FileSpan get nameSpan =>
36+
defaultValue == null ? span : span.initialIdentifier(includeLeading: 1);
37+
3338
Argument(this.name, this.span, {this.defaultValue});
3439

3540
String toString() => defaultValue == null ? name : "$name: $defaultValue";

lib/src/ast/sass/argument_declaration.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../../logger.dart';
1010
import '../../parse/scss.dart';
1111
import '../../utils.dart';
1212
import '../../util/character.dart';
13+
import '../../util/span.dart';
1314
import 'argument.dart';
1415
import 'node.dart';
1516

lib/src/ast/sass/configured_variable.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
import 'package:meta/meta.dart';
66
import 'package:source_span/source_span.dart';
77

8+
import '../../util/span.dart';
89
import 'expression.dart';
10+
import 'declaration.dart';
911
import 'node.dart';
1012

1113
/// A variable configured by a `with` clause in a `@use` or `@forward` rule.
1214
///
1315
/// {@category AST}
1416
@sealed
15-
class ConfiguredVariable implements SassNode {
17+
class ConfiguredVariable implements SassNode, SassDeclaration {
1618
/// The name of the variable being configured.
1719
final String name;
1820

@@ -26,6 +28,8 @@ class ConfiguredVariable implements SassNode {
2628

2729
final FileSpan span;
2830

31+
FileSpan get nameSpan => span.initialIdentifier(includeLeading: 1);
32+
2933
ConfiguredVariable(this.name, this.expression, this.span,
3034
{bool guarded = false})
3135
: isGuarded = guarded;

lib/src/ast/sass/declaration.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2021 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'package:meta/meta.dart';
6+
import 'package:source_span/source_span.dart';
7+
8+
import 'node.dart';
9+
10+
/// A common interface for any node that declares a Sass member.
11+
///
12+
/// {@category AST}
13+
@sealed
14+
abstract class SassDeclaration extends SassNode {
15+
/// The name of the declaration, with underscores converted to hyphens.
16+
///
17+
/// This does not include the `$` for variables.
18+
String get name;
19+
20+
/// The span containing this declaration's name.
21+
///
22+
/// This includes the `$` for variables.
23+
FileSpan get nameSpan;
24+
}

lib/src/ast/sass/dependency.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2021 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'package:meta/meta.dart';
6+
import 'package:source_span/source_span.dart';
7+
8+
import 'node.dart';
9+
10+
/// A common interface for [UseRule]s, [ForwardRule]s, and [DynamicImport]s.
11+
///
12+
/// {@category AST}
13+
@sealed
14+
abstract class SassDependency extends SassNode {
15+
/// The URL of the dependency this rule loads.
16+
Uri get url;
17+
18+
/// The span of the URL for this dependency, including the quotes.
19+
FileSpan get urlSpan;
20+
}

lib/src/ast/sass/expression/function.dart

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,58 @@
55
import 'package:meta/meta.dart';
66
import 'package:source_span/source_span.dart';
77

8+
import '../../../util/span.dart';
89
import '../../../visitor/interface/expression.dart';
910
import '../expression.dart';
1011
import '../argument_invocation.dart';
1112
import '../callable_invocation.dart';
12-
import '../interpolation.dart';
13+
import '../reference.dart';
1314

1415
/// A function invocation.
1516
///
16-
/// This may be a plain CSS function or a Sass function.
17+
/// This may be a plain CSS function or a Sass function, but may not include
18+
/// interpolation.
1719
///
1820
/// {@category AST}
1921
@sealed
20-
class FunctionExpression implements Expression, CallableInvocation {
22+
class FunctionExpression
23+
implements Expression, CallableInvocation, SassReference {
2124
/// The namespace of the function being invoked, or `null` if it's invoked
2225
/// without a namespace.
2326
final String? namespace;
2427

25-
/// The name of the function being invoked.
26-
///
27-
/// If [namespace] is non-`null`, underscores are converted to hyphens in this name.
28-
/// If [namespace] is `null`, this could be a plain CSS function call, so underscores are kept unchanged.
29-
///
30-
/// If this is interpolated, the function will be interpreted as plain CSS,
31-
/// even if it has the same name as a Sass function.
32-
final Interpolation name;
28+
/// The name of the function being invoked, with underscores left as-is.
29+
final String originalName;
3330

3431
/// The arguments to pass to the function.
3532
final ArgumentInvocation arguments;
3633

3734
final FileSpan span;
3835

39-
FunctionExpression(this.name, this.arguments, this.span, {this.namespace});
36+
/// The name of the function being invoked, with underscores converted to
37+
/// hyphens.
38+
///
39+
/// If this function is a plain CSS function, use [originalName] instead.
40+
String get name => originalName.replaceAll('_', '-');
41+
42+
FileSpan get nameSpan {
43+
if (namespace == null) return span.initialIdentifier();
44+
return span.withoutNamespace().initialIdentifier();
45+
}
46+
47+
FileSpan? get namespaceSpan =>
48+
namespace == null ? null : span.initialIdentifier();
49+
50+
FunctionExpression(this.originalName, this.arguments, this.span,
51+
{this.namespace});
4052

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

4456
String toString() {
4557
var buffer = StringBuffer();
4658
if (namespace != null) buffer.write("$namespace.");
47-
buffer.write("$name$arguments");
59+
buffer.write("$originalName$arguments");
4860
return buffer.toString();
4961
}
5062
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2021 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'package:meta/meta.dart';
6+
import 'package:source_span/source_span.dart';
7+
8+
import '../../../visitor/interface/expression.dart';
9+
import '../expression.dart';
10+
import '../argument_invocation.dart';
11+
import '../callable_invocation.dart';
12+
import '../interpolation.dart';
13+
14+
/// An interpolated function invocation.
15+
///
16+
/// This is always a plain CSS function.
17+
///
18+
/// {@category AST}
19+
@sealed
20+
class InterpolatedFunctionExpression implements Expression, CallableInvocation {
21+
/// The name of the function being invoked.
22+
final Interpolation name;
23+
24+
/// The arguments to pass to the function.
25+
final ArgumentInvocation arguments;
26+
27+
final FileSpan span;
28+
29+
InterpolatedFunctionExpression(this.name, this.arguments, this.span);
30+
31+
T accept<T>(ExpressionVisitor<T> visitor) =>
32+
visitor.visitInterpolatedFunctionExpression(this);
33+
34+
String toString() => '$name$arguments';
35+
}

lib/src/ast/sass/expression/variable.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
import 'package:meta/meta.dart';
66
import 'package:source_span/source_span.dart';
77

8+
import '../../../util/span.dart';
89
import '../../../visitor/interface/expression.dart';
910
import '../expression.dart';
11+
import '../reference.dart';
1012

1113
/// A Sass variable.
1214
///
1315
/// {@category AST}
1416
@sealed
15-
class VariableExpression implements Expression {
17+
class VariableExpression implements Expression, SassReference {
1618
/// The namespace of the variable being referenced, or `null` if it's
1719
/// referenced without a namespace.
1820
final String? namespace;
@@ -22,6 +24,14 @@ class VariableExpression implements Expression {
2224

2325
final FileSpan span;
2426

27+
FileSpan get nameSpan {
28+
if (namespace == null) return span;
29+
return span.withoutNamespace();
30+
}
31+
32+
FileSpan? get namespaceSpan =>
33+
namespace == null ? null : span.initialIdentifier();
34+
2535
VariableExpression(this.name, this.span, {this.namespace});
2636

2737
T accept<T>(ExpressionVisitor<T> visitor) =>

lib/src/ast/sass/import/dynamic.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import 'package:meta/meta.dart';
66
import 'package:source_span/source_span.dart';
77

88
import '../expression/string.dart';
9+
import '../dependency.dart';
910
import '../import.dart';
1011

1112
/// An import that will load a Sass file at runtime.
1213
///
1314
/// {@category AST}
1415
@sealed
15-
class DynamicImport implements Import {
16+
class DynamicImport implements Import, SassDependency {
1617
/// The URL of the file to import.
1718
///
1819
/// If this is relative, it's relative to the containing file.
@@ -30,6 +31,7 @@ class DynamicImport implements Import {
3031
final String urlString;
3132

3233
final FileSpan span;
34+
FileSpan get urlSpan => span;
3335

3436
DynamicImport(this.urlString, this.span);
3537

lib/src/ast/sass/reference.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2021 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'package:meta/meta.dart';
6+
import 'package:source_span/source_span.dart';
7+
8+
import 'node.dart';
9+
10+
/// A common interface for any node that references a Sass member.
11+
///
12+
/// {@category AST}
13+
@sealed
14+
abstract class SassReference extends SassNode {
15+
/// The namespace of the member being referenced, or `null` if it's referenced
16+
/// without a namespace.
17+
String? get namespace;
18+
19+
/// The name of the member being referenced, with underscores converted to
20+
/// hyphens.
21+
///
22+
/// This does not include the `$` for variables.
23+
String get name;
24+
25+
/// The span containing this reference's name.
26+
///
27+
/// For variables, this should include the `$`.
28+
FileSpan get nameSpan;
29+
30+
/// The span containing this reference's namespace, null if [namespace] is
31+
/// null.
32+
FileSpan? get namespaceSpan;
33+
}

lib/src/ast/sass/statement/forward_rule.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ import 'package:collection/collection.dart';
66
import 'package:meta/meta.dart';
77
import 'package:source_span/source_span.dart';
88

9+
import '../../../util/span.dart';
910
import '../../../visitor/interface/statement.dart';
1011
import '../configured_variable.dart';
12+
import '../dependency.dart';
1113
import '../expression/string.dart';
1214
import '../statement.dart';
1315

1416
/// A `@forward` rule.
1517
///
1618
/// {@category AST}
1719
@sealed
18-
class ForwardRule implements Statement {
20+
class ForwardRule implements Statement, SassDependency {
1921
/// The URI of the module to forward.
2022
///
2123
/// If this is relative, it's relative to the containing file.
@@ -74,6 +76,8 @@ class ForwardRule implements Statement {
7476

7577
final FileSpan span;
7678

79+
FileSpan get urlSpan => span.withoutInitialAtRule().initialQuoted();
80+
7781
/// Creates a `@forward` rule that allows all members to be accessed.
7882
ForwardRule(this.url, this.span,
7983
{this.prefix, Iterable<ConfiguredVariable>? configuration})

lib/src/ast/sass/statement/function_rule.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import 'package:meta/meta.dart';
66
import 'package:source_span/source_span.dart';
77

8+
import '../../../util/span.dart';
89
import '../../../visitor/interface/statement.dart';
910
import '../argument_declaration.dart';
11+
import '../declaration.dart';
1012
import '../statement.dart';
1113
import 'callable_declaration.dart';
1214
import 'silent_comment.dart';
@@ -17,7 +19,9 @@ import 'silent_comment.dart';
1719
///
1820
/// {@category AST}
1921
@sealed
20-
class FunctionRule extends CallableDeclaration {
22+
class FunctionRule extends CallableDeclaration implements SassDeclaration {
23+
FileSpan get nameSpan => span.withoutInitialAtRule().initialIdentifier();
24+
2125
FunctionRule(String name, ArgumentDeclaration arguments,
2226
Iterable<Statement> children, FileSpan span,
2327
{SilentComment? comment})

0 commit comments

Comments
 (0)