OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library services.src.correction.statement_analyzer; | |
6 | |
7 import 'package:analysis_services/correction/status.dart'; | |
8 import 'package:analysis_services/src/correction/selection_analyzer.dart'; | |
9 import 'package:analysis_services/src/correction/source_range.dart'; | |
10 import 'package:analysis_services/src/correction/util.dart'; | |
11 import 'package:analyzer/src/generated/ast.dart'; | |
12 import 'package:analyzer/src/generated/element.dart'; | |
13 import 'package:analyzer/src/generated/scanner.dart'; | |
14 import 'package:analyzer/src/generated/source.dart'; | |
15 | |
16 | |
17 /** | |
18 * Returns [Token]s of the given Dart source, not `null`, may be empty if no | |
19 * tokens or some exception happens. | |
20 */ | |
21 List<Token> _getTokens(String text) { | |
22 try { | |
23 List<Token> tokens = <Token>[]; | |
24 Scanner scanner = new Scanner(null, new CharSequenceReader(text), null); | |
25 Token token = scanner.tokenize(); | |
26 while (token.type != TokenType.EOF) { | |
27 tokens.add(token); | |
28 token = token.next; | |
29 } | |
30 return tokens; | |
31 } catch (e) { | |
32 return new List<Token>(0); | |
33 } | |
34 } | |
35 | |
36 | |
37 /** | |
38 * Analyzer to check if a selection covers a valid set of statements of AST. | |
39 */ | |
40 class StatementAnalyzer extends SelectionAnalyzer { | |
41 final CompilationUnit unit; | |
42 | |
43 RefactoringStatus _status = new RefactoringStatus(); | |
44 | |
45 StatementAnalyzer(this.unit, SourceRange selection) : super(selection); | |
46 | |
47 /** | |
48 * Returns the [RefactoringStatus] result of selection checking. | |
49 */ | |
50 RefactoringStatus get status => _status; | |
51 | |
52 /** | |
53 * Records fatal error with given message. | |
54 */ | |
55 void invalidSelection(String message) { | |
56 invalidSelection2(message, null); | |
57 } | |
58 | |
59 /** | |
60 * Records fatal error with given message and [RefactoringStatusContext]. | |
61 */ | |
62 void invalidSelection2(String message, RefactoringStatusContext context) { | |
63 _status.addFatalError(message, context); | |
64 reset(); | |
65 } | |
66 | |
67 @override | |
68 Object visitCompilationUnit(CompilationUnit node) { | |
69 super.visitCompilationUnit(node); | |
70 if (!hasSelectedNodes) { | |
71 return null; | |
72 } | |
73 // check that selection does not begin/end in comment | |
74 { | |
75 int selectionStart = selection.offset; | |
76 int selectionEnd = selection.end; | |
77 List<SourceRange> commentRanges = getCommentRanges(unit); | |
78 for (SourceRange commentRange in commentRanges) { | |
79 if (commentRange.contains(selectionStart)) { | |
80 invalidSelection("Selection begins inside a comment."); | |
81 } | |
82 if (commentRange.containsExclusive(selectionEnd)) { | |
83 invalidSelection("Selection ends inside a comment."); | |
84 } | |
85 } | |
86 } | |
87 // more checks | |
88 if (!_status.hasFatalError) { | |
89 _checkSelectedNodes(node); | |
90 } | |
91 return null; | |
92 } | |
93 | |
94 @override | |
95 Object visitDoStatement(DoStatement node) { | |
96 super.visitDoStatement(node); | |
97 List<AstNode> selectedNodes = this.selectedNodes; | |
98 if (_contains(selectedNodes, node.body)) { | |
99 invalidSelection( | |
100 "Operation not applicable to a 'do' statement's body and expression.")
; | |
101 } | |
102 return null; | |
103 } | |
104 | |
105 @override | |
106 Object visitForStatement(ForStatement node) { | |
107 super.visitForStatement(node); | |
108 List<AstNode> selectedNodes = this.selectedNodes; | |
109 bool containsInit = | |
110 _contains(selectedNodes, node.initialization) || | |
111 _contains(selectedNodes, node.variables); | |
112 bool containsCondition = _contains(selectedNodes, node.condition); | |
113 bool containsUpdaters = _containsAny(selectedNodes, node.updaters); | |
114 bool containsBody = _contains(selectedNodes, node.body); | |
115 if (containsInit && containsCondition) { | |
116 invalidSelection( | |
117 "Operation not applicable to a 'for' statement's initializer and condi
tion."); | |
118 } else if (containsCondition && containsUpdaters) { | |
119 invalidSelection( | |
120 "Operation not applicable to a 'for' statement's condition and updater
s."); | |
121 } else if (containsUpdaters && containsBody) { | |
122 invalidSelection( | |
123 "Operation not applicable to a 'for' statement's updaters and body."); | |
124 } | |
125 return null; | |
126 } | |
127 | |
128 @override | |
129 Object visitSwitchStatement(SwitchStatement node) { | |
130 super.visitSwitchStatement(node); | |
131 List<AstNode> selectedNodes = this.selectedNodes; | |
132 List<SwitchMember> switchMembers = node.members; | |
133 for (AstNode selectedNode in selectedNodes) { | |
134 if (switchMembers.contains(selectedNode)) { | |
135 invalidSelection( | |
136 "Selection must either cover whole switch statement or parts of a si
ngle case block."); | |
137 break; | |
138 } | |
139 } | |
140 return null; | |
141 } | |
142 | |
143 @override | |
144 Object visitTryStatement(TryStatement node) { | |
145 super.visitTryStatement(node); | |
146 AstNode firstSelectedNode = this.firstSelectedNode; | |
147 if (firstSelectedNode != null) { | |
148 if (firstSelectedNode == node.body || | |
149 firstSelectedNode == node.finallyBlock) { | |
150 invalidSelection( | |
151 "Selection must either cover whole try statement or parts of try, ca
tch, or finally block."); | |
152 } else { | |
153 List<CatchClause> catchClauses = node.catchClauses; | |
154 for (CatchClause catchClause in catchClauses) { | |
155 if (firstSelectedNode == catchClause || | |
156 firstSelectedNode == catchClause.body || | |
157 firstSelectedNode == catchClause.exceptionParameter) { | |
158 invalidSelection( | |
159 "Selection must either cover whole try statement or parts of try
, catch, or finally block."); | |
160 } | |
161 } | |
162 } | |
163 } | |
164 return null; | |
165 } | |
166 | |
167 @override | |
168 Object visitWhileStatement(WhileStatement node) { | |
169 super.visitWhileStatement(node); | |
170 List<AstNode> selectedNodes = this.selectedNodes; | |
171 if (_contains(selectedNodes, node.condition) && | |
172 _contains(selectedNodes, node.body)) { | |
173 invalidSelection( | |
174 "Operation not applicable to a while statement's expression and body."
); | |
175 } | |
176 return null; | |
177 } | |
178 | |
179 /** | |
180 * Checks final selected [AstNode]s after processing [CompilationUnit]. | |
181 */ | |
182 void _checkSelectedNodes(CompilationUnit unit) { | |
183 List<AstNode> nodes = selectedNodes; | |
184 // some tokens before first selected node | |
185 { | |
186 AstNode firstNode = nodes[0]; | |
187 SourceRange rangeBeforeFirstNode = rangeStartStart(selection, firstNode); | |
188 if (_hasTokens(rangeBeforeFirstNode)) { | |
189 invalidSelection2( | |
190 "The beginning of the selection contains characters that do not belo
ng to a statement.", | |
191 new RefactoringStatusContext.forUnit(unit, rangeBeforeFirstNode)); | |
192 } | |
193 } | |
194 // some tokens after last selected node | |
195 { | |
196 AstNode lastNode = nodes.last; | |
197 SourceRange rangeAfterLastNode = rangeEndEnd(lastNode, selection); | |
198 if (_hasTokens(rangeAfterLastNode)) { | |
199 invalidSelection2( | |
200 "The end of the selection contains characters that do not belong to
a statement.", | |
201 new RefactoringStatusContext.forUnit(unit, rangeAfterLastNode)); | |
202 } | |
203 } | |
204 } | |
205 | |
206 /** | |
207 * Returns `true` if there are [Token]s in the given [SourceRange]. | |
208 */ | |
209 bool _hasTokens(SourceRange range) { | |
210 CompilationUnitElement unitElement = unit.element; | |
211 String fullText = unitElement.context.getContents(unitElement.source).data; | |
212 String rangeText = fullText.substring(range.offset, range.end); | |
213 return _getTokens(rangeText).isNotEmpty; | |
214 } | |
215 | |
216 /** | |
217 * Returns `true` if [nodes] contains [node]. | |
218 */ | |
219 static bool _contains(List<AstNode> nodes, AstNode node) => | |
220 nodes.contains(node); | |
221 | |
222 /** | |
223 * Returns `true` if [nodes] contains one of the [otherNodes]. | |
224 */ | |
225 static bool _containsAny(List<AstNode> nodes, List<AstNode> otherNodes) { | |
226 for (AstNode otherNode in otherNodes) { | |
227 if (nodes.contains(otherNode)) { | |
228 return true; | |
229 } | |
230 } | |
231 return false; | |
232 } | |
233 } | |
OLD | NEW |