-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
scrape_visitor.dart
164 lines (134 loc) · 5.45 KB
/
scrape_visitor.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/source/line_info.dart';
import '../scrape.dart';
/// Wire up [visitor] to [scrape] the given [path] containing [source] with
/// [info].
///
/// This is a top-level function instead of an instance method so that we can
/// hide it and not export it from scrape's public API. Only [Scrape] itself
/// should call this. We bind separately instead of passing these through the
/// [ScrapeVisitor] constructor so that subclasses of [ScrapeVisitor] don't
/// need to define a pass-through constructor.
void bindVisitor(ScrapeVisitor visitor, Scrape scrape, String path,
String source, Token startToken, LineInfo info) {
visitor._scrape = scrape;
visitor._path = path;
visitor._source = source;
visitor._startToken = startToken;
visitor.lineInfo = info;
}
/// Base Visitor class with some utility functionality.
class ScrapeVisitor extends RecursiveAstVisitor<void> {
// These are initialized by [bindVisitor()].
late final Scrape _scrape;
late final String _path;
late final String _source;
late final Token _startToken;
late final LineInfo lineInfo;
/// How many levels deep the visitor is currently nested inside build methods.
int _inFlutterBuildMethods = 0;
/// The path to the file being visited.
String get path => _path;
/// The source code of the file.
String get source => _source;
Token get startToken => _startToken;
// TODO(rnystrom): Remove this in favor of using surveyor for these kinds of
// analyses.
/// Whether the visitor is currently inside Flutter's "build" method,
/// either directly or nested inside some other function inside one.
///
/// This is only an approximate guess. It assumes a method is a "build"-like
/// method if it returns "Widget", or has a parameter list that starts with
/// "BuildContext context".
bool get isInFlutterBuildMethod => _inFlutterBuildMethods > 0;
bool _isBuildMethod(
TypeAnnotation? returnType, FormalParameterList? parameters) {
var parameterString = parameters.toString();
if (returnType.toString() == 'void') return false;
if (parameterString.startsWith('(BuildContext context')) return true;
if (returnType.toString() == 'Widget') return true;
return false;
}
/// Add an occurrence of [item] to [histogram].
void record(String histogram, Object item) {
_scrape.record(histogram, item);
}
/// Write [message] to stdout, clearing the current line if needed.
void log(Object message) {
_scrape.log(message);
}
/// Print a nice representation of [node].
void printNode(AstNode node) {
log(nodeToString(node));
}
/// Print the lines of code containing offsets [start] through [end].
void printRange(int start, int end) {
log(rangeToString(start, end));
}
/// Print the line containing [token].
void printToken(Token token) {
printRange(token.offset, token.end);
}
/// Generate a nice string representation of [node] include file path and
/// line information.
String nodeToString(AstNode node) {
return rangeToString(node.offset, node.end);
}
/// Generate a string with the file path and lines of source code
/// that contain the source character offsets from [start] to [end].
String rangeToString(int start, int end) {
var startLine = lineInfo.getLocation(start).lineNumber;
var endLine = lineInfo.getLocation(end).lineNumber;
startLine = startLine.clamp(0, lineInfo.lineCount - 1);
endLine = endLine.clamp(0, lineInfo.lineCount - 1);
var buffer = StringBuffer();
buffer.writeln('// $path:$startLine');
for (var line = startLine; line <= endLine; line++) {
// Note that getLocation() returns 1-based lines, but getOffsetOfLine()
// expects 0-based.
var offset = lineInfo.getOffsetOfLine(line - 1);
// -1 to not include the newline.
var end = lineInfo.getOffsetOfLine(line) - 1;
buffer.writeln(_source.substring(offset, end));
}
return buffer.toString();
}
/// Get the line number of the code at [offset].
int getLine(int offset) => lineInfo.getLocation(offset).lineNumber;
/// Override this to execute custom code before visiting a Flutter build
/// method.
void beforeVisitBuildMethod(Declaration node) {}
/// Override this to execute custom code after visiting a Flutter build
/// method.
void afterVisitBuildMethod(Declaration node) {}
@override
void visitMethodDeclaration(MethodDeclaration node) {
var isBuild = _isBuildMethod(node.returnType, node.parameters);
if (isBuild) _inFlutterBuildMethods++;
try {
if (isBuild) beforeVisitBuildMethod(node);
super.visitMethodDeclaration(node);
if (isBuild) afterVisitBuildMethod(node);
} finally {
if (isBuild) _inFlutterBuildMethods--;
}
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
var isBuild =
_isBuildMethod(node.returnType, node.functionExpression.parameters);
if (isBuild) _inFlutterBuildMethods++;
try {
if (isBuild) beforeVisitBuildMethod(node);
super.visitFunctionDeclaration(node);
if (isBuild) afterVisitBuildMethod(node);
} finally {
if (isBuild) _inFlutterBuildMethods--;
}
}
}