Skip to content

Commit ecbf1ae

Browse files
authored
Add a RecursiveStatementVisitor (#313)
This will make it easier to write utilities that traverse the syntax tree.
1 parent 166dac6 commit ecbf1ae

File tree

6 files changed

+232
-16
lines changed

6 files changed

+232
-16
lines changed

lib/src/ast/sass.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export 'sass/argument.dart';
66
export 'sass/argument_declaration.dart';
77
export 'sass/argument_invocation.dart';
88
export 'sass/at_root_query.dart';
9-
export 'sass/callable_declaration.dart';
109
export 'sass/callable_invocation.dart';
1110
export 'sass/expression.dart';
1211
export 'sass/expression/binary_operation.dart';
@@ -31,6 +30,7 @@ export 'sass/node.dart';
3130
export 'sass/statement.dart';
3231
export 'sass/statement/at_root_rule.dart';
3332
export 'sass/statement/at_rule.dart';
33+
export 'sass/statement/callable_declaration.dart';
3434
export 'sass/statement/content_rule.dart';
3535
export 'sass/statement/debug_rule.dart';
3636
export 'sass/statement/declaration.dart';
@@ -45,6 +45,7 @@ export 'sass/statement/include_rule.dart';
4545
export 'sass/statement/loud_comment.dart';
4646
export 'sass/statement/media_rule.dart';
4747
export 'sass/statement/mixin_rule.dart';
48+
export 'sass/statement/parent.dart';
4849
export 'sass/statement/return_rule.dart';
4950
export 'sass/statement/silent_comment.dart';
5051
export 'sass/statement/style_rule.dart';

lib/src/ast/sass/callable_declaration.dart renamed to lib/src/ast/sass/statement/callable_declaration.dart

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,22 @@
44

55
import 'package:source_span/source_span.dart';
66

7-
import 'argument_declaration.dart';
8-
import 'statement.dart';
7+
import '../argument_declaration.dart';
8+
import '../statement.dart';
9+
import 'parent.dart';
910

1011
/// An abstract class for callables (functions or mixins) that are declared in
1112
/// user code.
12-
abstract class CallableDeclaration implements Statement {
13+
abstract class CallableDeclaration extends ParentStatement {
1314
/// The name of this callable.
1415
final String name;
1516

1617
/// The declared arguments this callable accepts.
1718
final ArgumentDeclaration arguments;
1819

19-
/// The child statements that are executed when this callable is invoked.
20-
final List<Statement> children;
21-
2220
final FileSpan span;
2321

2422
CallableDeclaration(
2523
this.name, this.arguments, Iterable<Statement> children, this.span)
26-
: children = new List.unmodifiable(children);
24+
: super(new List.unmodifiable(children));
2725
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import 'package:source_span/source_span.dart';
66

77
import '../../../visitor/interface/statement.dart';
88
import '../argument_declaration.dart';
9-
import '../callable_declaration.dart';
109
import '../statement.dart';
10+
import 'callable_declaration.dart';
1111

1212
/// A function declaration.
1313
///

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,21 @@ import '../../../visitor/interface/statement.dart';
88
import '../argument_invocation.dart';
99
import '../callable_invocation.dart';
1010
import '../statement.dart';
11+
import 'parent.dart';
1112

1213
/// A mixin invocation.
13-
class IncludeRule implements Statement, CallableInvocation {
14+
class IncludeRule extends ParentStatement implements CallableInvocation {
1415
/// The name of the mixin being invoked.
1516
final String name;
1617

1718
/// The arguments to pass to the mixin.
1819
final ArgumentInvocation arguments;
1920

20-
/// The content block to pass to the mixin, or `null` if there is no content
21-
/// block.
22-
final List<Statement> children;
23-
2421
final FileSpan span;
2522

2623
IncludeRule(this.name, this.arguments, this.span,
2724
{Iterable<Statement> children})
28-
: children = children == null ? null : new List.unmodifiable(children);
25+
: super(children == null ? null : new List.unmodifiable(children));
2926

3027
T accept<T>(StatementVisitor<T> visitor) => visitor.visitIncludeRule(this);
3128

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import 'package:source_span/source_span.dart';
66

77
import '../../../visitor/interface/statement.dart';
8-
import '../callable_declaration.dart';
98
import '../argument_declaration.dart';
109
import '../statement.dart';
10+
import 'callable_declaration.dart';
1111

1212
/// A mixin declaration.
1313
///
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Copyright 2018 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+
7+
import '../ast/sass.dart';
8+
import 'interface/statement.dart';
9+
10+
/// A visitor that recursively traverses each statement in a Sass AST.
11+
///
12+
/// In addition to the methods from [StatementVisitor], this has more general
13+
/// protected methods that can be overriden to add behavior for a wide variety
14+
/// of AST nodes:
15+
///
16+
/// * [visitCallableDeclaration]
17+
/// * [visitSupportsCondition]
18+
/// * [visitChildren]
19+
/// * [visitInterpolation]
20+
/// * [visitExpression]
21+
///
22+
/// The default implementation of the visit methods all return `null`.
23+
abstract class RecursiveStatementVisitor<T> implements StatementVisitor<T> {
24+
T visitAtRootRule(AtRootRule node) {
25+
visitInterpolation(node.query);
26+
return visitChildren(node);
27+
}
28+
29+
T visitAtRule(AtRule node) {
30+
visitInterpolation(node.value);
31+
return visitChildren(node);
32+
}
33+
34+
T visitContentRule(ContentRule node) => null;
35+
36+
T visitDebugRule(DebugRule node) {
37+
visitExpression(node.expression);
38+
return null;
39+
}
40+
41+
T visitDeclaration(Declaration node) {
42+
visitInterpolation(node.name);
43+
visitExpression(node.value);
44+
return visitChildren(node);
45+
}
46+
47+
T visitEachRule(EachRule node) {
48+
visitExpression(node.list);
49+
return visitChildren(node);
50+
}
51+
52+
T visitErrorRule(ErrorRule node) {
53+
visitExpression(node.expression);
54+
return null;
55+
}
56+
57+
T visitExtendRule(ExtendRule node) {
58+
visitInterpolation(node.selector);
59+
return null;
60+
}
61+
62+
T visitForRule(ForRule node) {
63+
visitExpression(node.from);
64+
visitExpression(node.to);
65+
return visitChildren(node);
66+
}
67+
68+
T visitFunctionRule(FunctionRule node) => visitCallableDeclaration(node);
69+
70+
T visitIfRule(IfRule node) {
71+
for (var clause in node.clauses) {
72+
_visitIfClause(clause);
73+
}
74+
if (node.lastClause != null) _visitIfClause(node.lastClause);
75+
return null;
76+
}
77+
78+
/// Visits [clause]'s expression and children.
79+
void _visitIfClause(IfClause clause) {
80+
if (clause.expression != null) visitExpression(clause.expression);
81+
for (var child in clause.children) {
82+
child.accept(this);
83+
}
84+
}
85+
86+
T visitImportRule(ImportRule node) {
87+
for (var import in node.imports) {
88+
if (import is StaticImport) {
89+
visitInterpolation(import.url);
90+
if (import.supports != null) visitSupportsCondition(import.supports);
91+
if (import.media != null) visitInterpolation(import.media);
92+
}
93+
}
94+
return null;
95+
}
96+
97+
T visitIncludeRule(IncludeRule node) {
98+
for (var expression in node.arguments.positional) {
99+
visitExpression(expression);
100+
}
101+
for (var expression in node.arguments.named.values) {
102+
visitExpression(expression);
103+
}
104+
if (node.arguments.rest != null) {
105+
visitExpression(node.arguments.rest);
106+
}
107+
if (node.arguments.keywordRest != null) {
108+
visitExpression(node.arguments.keywordRest);
109+
}
110+
111+
return node.children == null ? null : visitChildren(node);
112+
}
113+
114+
T visitLoudComment(LoudComment node) {
115+
visitInterpolation(node.text);
116+
return null;
117+
}
118+
119+
T visitMediaRule(MediaRule node) {
120+
visitInterpolation(node.query);
121+
return visitChildren(node);
122+
}
123+
124+
T visitMixinRule(MixinRule node) => visitCallableDeclaration(node);
125+
126+
T visitReturnRule(ReturnRule node) {
127+
visitExpression(node.expression);
128+
return null;
129+
}
130+
131+
T visitSilentComment(SilentComment node) => null;
132+
133+
T visitStyleRule(StyleRule node) {
134+
visitInterpolation(node.selector);
135+
return visitChildren(node);
136+
}
137+
138+
T visitStylesheet(Stylesheet node) => visitChildren(node);
139+
140+
T visitSupportsRule(SupportsRule node) {
141+
visitSupportsCondition(node.condition);
142+
return visitChildren(node);
143+
}
144+
145+
T visitVariableDeclaration(VariableDeclaration node) {
146+
visitExpression(node.expression);
147+
return null;
148+
}
149+
150+
T visitWarnRule(WarnRule node) {
151+
visitExpression(node.expression);
152+
return null;
153+
}
154+
155+
T visitWhileRule(WhileRule node) {
156+
visitExpression(node.condition);
157+
return visitChildren(node);
158+
}
159+
160+
/// Visits each of [node]'s expressions and children.
161+
///
162+
/// The default implementations of [visitFunctionRule] and [visitMixinRule]
163+
/// call this.
164+
@protected
165+
T visitCallableDeclaration(CallableDeclaration node) {
166+
for (var argument in node.arguments.arguments) {
167+
if (argument.defaultValue != null) visitExpression(argument.defaultValue);
168+
}
169+
return visitChildren(node);
170+
}
171+
172+
/// Visits each expression in [condition].
173+
///
174+
/// The default implementation of the visit methods call this to visit any
175+
/// [SupportsCondition] they encounter.
176+
@protected
177+
void visitSupportsCondition(SupportsCondition condition) {
178+
if (condition is SupportsOperation) {
179+
visitSupportsCondition(condition.left);
180+
visitSupportsCondition(condition.right);
181+
} else if (condition is SupportsNegation) {
182+
visitSupportsCondition(condition.condition);
183+
} else if (condition is SupportsInterpolation) {
184+
visitExpression(condition.expression);
185+
} else if (condition is SupportsDeclaration) {
186+
visitExpression(condition.name);
187+
visitExpression(condition.value);
188+
}
189+
}
190+
191+
/// Visits each of [node]'s children.
192+
///
193+
/// The default implementation of the visit methods for all [ParentStatement]s
194+
/// call this and return its result.
195+
@protected
196+
T visitChildren(ParentStatement node) {
197+
for (var child in node.children) {
198+
child.accept(this);
199+
}
200+
return null;
201+
}
202+
203+
/// Visits each expression in an [interpolation].
204+
///
205+
/// The default implementation of the visit methods call this to visit any
206+
/// interpolation in a statement.
207+
@protected
208+
void visitInterpolation(Interpolation interpolation) {
209+
for (var node in interpolation.contents) {
210+
if (node is Expression) visitExpression(node);
211+
}
212+
}
213+
214+
/// Visits [expression].
215+
///
216+
/// The default implementation of the visit methods call this to visit any
217+
/// expression in a statement.
218+
@protected
219+
void visitExpression(Expression expression) {}
220+
}

0 commit comments

Comments
 (0)