| 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 |