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 // This code was auto-generated, is not intended to be edited, and is subject to | |
6 // significant change. Please see the README file for more information. | |
7 | |
8 library services.util; | |
9 | |
10 import 'dart:collection'; | |
11 import 'package:analyzer/src/generated/java_core.dart' hide StringUtils; | |
12 import 'package:analyzer/src/generated/ast.dart'; | |
13 import 'package:analyzer/src/generated/element.dart'; | |
14 import 'package:analyzer/src/generated/engine.dart'; | |
15 import 'package:analyzer/src/generated/error.dart'; | |
16 import 'package:analyzer/src/generated/resolver.dart'; | |
17 import 'package:analyzer/src/generated/source.dart'; | |
18 import 'package:analyzer/src/generated/scanner.dart'; | |
19 import 'change.dart'; | |
20 import 'proposal.dart'; | |
21 import 'status.dart'; | |
22 import 'stubs.dart'; | |
23 | |
24 /** | |
25 * Context for which assistance should be provided. | |
26 */ | |
27 class AssistContext { | |
28 final SearchEngine searchEngine; | |
29 | |
30 final AnalysisContext analysisContext; | |
31 | |
32 final String analysisContextId; | |
33 | |
34 final Source source; | |
35 | |
36 final CompilationUnit compilationUnit; | |
37 | |
38 final int selectionOffset; | |
39 | |
40 final int selectionLength; | |
41 | |
42 AstNode _coveredNode; | |
43 | |
44 AstNode _coveringNode; | |
45 | |
46 Element _coveredElement; | |
47 | |
48 bool _coveredElementFound = false; | |
49 | |
50 AssistContext.con1(this.searchEngine, this.analysisContext, this.analysisConte
xtId, this.source, this.compilationUnit, this.selectionOffset, this.selectionLen
gth); | |
51 | |
52 AssistContext.con2(SearchEngine searchEngine, AnalysisContext analysisContext,
String analysisContextId, Source source, CompilationUnit compilationUnit, Sourc
eRange selectionRange) : this.con1(searchEngine, analysisContext, analysisContex
tId, source, compilationUnit, selectionRange.offset, selectionRange.length); | |
53 | |
54 /** | |
55 * @return the resolved [CompilationUnitElement] of the [Source]. | |
56 */ | |
57 CompilationUnitElement get compilationUnitElement => compilationUnit.element; | |
58 | |
59 /** | |
60 * @return the [Element] of the [coveredNode], may be <code>null</code>. | |
61 */ | |
62 Element get coveredElement { | |
63 if (!_coveredElementFound) { | |
64 _coveredElementFound = true; | |
65 AstNode coveredNode = this.coveredNode; | |
66 if (coveredNode == null) { | |
67 return null; | |
68 } | |
69 _coveredElement = ElementLocator.locateWithOffset(coveredNode, selectionOf
fset); | |
70 } | |
71 return _coveredElement; | |
72 } | |
73 | |
74 /** | |
75 * @return the [AstNode] that is covered by the selection. | |
76 */ | |
77 AstNode get coveredNode { | |
78 if (_coveredNode == null) { | |
79 NodeLocator locator = new NodeLocator.con2(selectionOffset, selectionOffse
t); | |
80 _coveredNode = locator.searchWithin(compilationUnit); | |
81 } | |
82 return _coveredNode; | |
83 } | |
84 | |
85 /** | |
86 * @return the ASTNode that covers the selection. | |
87 */ | |
88 AstNode get coveringNode { | |
89 if (_coveringNode == null) { | |
90 NodeLocator locator = new NodeLocator.con2(selectionOffset, selectionOffse
t + selectionLength); | |
91 _coveringNode = locator.searchWithin(compilationUnit); | |
92 } | |
93 return _coveringNode; | |
94 } | |
95 | |
96 /** | |
97 * @return the errors associated with the [Source]. | |
98 */ | |
99 List<AnalysisError> get errors { | |
100 Source source = this.source; | |
101 if (analysisContext == null || source == null) { | |
102 return AnalysisError.NO_ERRORS; | |
103 } | |
104 return analysisContext.getErrors(source).errors; | |
105 } | |
106 | |
107 /** | |
108 * @return the [SourceRange] of the selection. | |
109 */ | |
110 SourceRange get selectionRange => new SourceRange(selectionOffset, selectionLe
ngth); | |
111 } | |
112 | |
113 /** | |
114 * Utilities for analyzing [CompilationUnit], its parts and source. | |
115 */ | |
116 class CorrectionUtils { | |
117 /** | |
118 * If `true` then [addEdit] validates that | |
119 * [Edit] replaces correct part of the [Source]. | |
120 */ | |
121 static bool _DEBUG_VALIDATE_EDITS = true; | |
122 | |
123 static List<String> _KNOWN_METHOD_NAME_PREFIXES = ["get", "is", "to"]; | |
124 | |
125 /** | |
126 * Validates that the [Edit] replaces the expected part of the [Source] and ad
ds this | |
127 * [Edit] to the [SourceChange]. | |
128 */ | |
129 static void addEdit(AnalysisContext context, SourceChange change, String descr
iption, String expected, Edit edit) { | |
130 if (_DEBUG_VALIDATE_EDITS) { | |
131 Source source = change.source; | |
132 String sourceContent = getSourceContent(context, source); | |
133 // prepare range | |
134 int beginIndex = edit.offset; | |
135 int endIndex = beginIndex + edit.length; | |
136 int sourceLength = sourceContent.length; | |
137 if (beginIndex >= sourceLength || endIndex >= sourceLength) { | |
138 throw new IllegalStateException("${source} has ${sourceLength} character
s but ${beginIndex} to ${endIndex} requested.\n\nTry to use Tools | Reanalyze So
urces."); | |
139 } | |
140 // check that range has expected content | |
141 String rangeContent = sourceContent.substring(beginIndex, endIndex); | |
142 if (rangeContent != expected) { | |
143 throw new IllegalStateException("${source} expected |${expected}| at ${b
eginIndex} to ${endIndex} but |${rangeContent}| found.\n\nTry to use Tools | Rea
nalyze Sources."); | |
144 } | |
145 } | |
146 // do add the Edit | |
147 change.addEdit(edit, description); | |
148 } | |
149 | |
150 /** | |
151 * @return <code>true</code> if given [List]s are equals at given position. | |
152 */ | |
153 static bool allListsEqual(List<List> lists, int position) { | |
154 Object element = lists[0][position]; | |
155 for (List list in lists) { | |
156 if (!identical(list[position], element)) { | |
157 return false; | |
158 } | |
159 } | |
160 return true; | |
161 } | |
162 | |
163 /** | |
164 * @return the updated [String] with applied [Edit]s. | |
165 */ | |
166 static String applyReplaceEdits(String s, List<Edit> edits) { | |
167 // sort edits | |
168 edits = []; | |
169 edits.sort((Edit o1, Edit o2) => o1.offset - o2.offset); | |
170 // apply edits | |
171 int delta = 0; | |
172 for (Edit edit in edits) { | |
173 int editOffset = edit.offset + delta; | |
174 String beforeEdit = s.substring(0, editOffset); | |
175 String afterEdit = s.substring(editOffset + edit.length); | |
176 s = "${beforeEdit}${edit.replacement}${afterEdit}"; | |
177 delta += getDeltaOffset(edit); | |
178 } | |
179 // done | |
180 return s; | |
181 } | |
182 | |
183 /** | |
184 * @return <code>true</code> if given [SourceRange] covers given [AstNode]. | |
185 */ | |
186 static bool covers(SourceRange r, AstNode node) { | |
187 SourceRange nodeRange = SourceRangeFactory.rangeNode(node); | |
188 return r.covers(nodeRange); | |
189 } | |
190 | |
191 /** | |
192 * @return all direct children of the given [Element]. | |
193 */ | |
194 static List<Element> getChildren(Element parent) => getChildren2(parent, null)
; | |
195 | |
196 /** | |
197 * @param name the required name of children; may be <code>null</code> to get
children with any | |
198 * name. | |
199 * @return all direct children of the given [Element], with given name. | |
200 */ | |
201 static List<Element> getChildren2(Element parent, String name) { | |
202 List<Element> children = []; | |
203 parent.accept(new GeneralizingElementVisitor_CorrectionUtils_getChildren(par
ent, name, children)); | |
204 return children; | |
205 } | |
206 | |
207 static String getDefaultValueCode(DartType type) { | |
208 if (type != null) { | |
209 String typeName = type.displayName; | |
210 if (typeName == "bool") { | |
211 return "false"; | |
212 } | |
213 if (typeName == "int") { | |
214 return "0"; | |
215 } | |
216 if (typeName == "double") { | |
217 return "0.0"; | |
218 } | |
219 if (typeName == "String") { | |
220 return "''"; | |
221 } | |
222 } | |
223 // no better guess | |
224 return "null"; | |
225 } | |
226 | |
227 /** | |
228 * @return the number of characters this [Edit] will move offsets after its ra
nge. | |
229 */ | |
230 static int getDeltaOffset(Edit edit) => edit.replacement.length - edit.length; | |
231 | |
232 /** | |
233 * @return the name of the [Element] kind. | |
234 */ | |
235 static String getElementKindName(Element element) { | |
236 ElementKind kind = element.kind; | |
237 return getElementKindName2(kind); | |
238 } | |
239 | |
240 /** | |
241 * @return the display name of the [ElementKind]. | |
242 */ | |
243 static String getElementKindName2(ElementKind kind) => kind.displayName; | |
244 | |
245 /** | |
246 * @return the human name of the [Element]. | |
247 */ | |
248 static String getElementQualifiedName(Element element) { | |
249 ElementKind kind = element.kind; | |
250 while (true) { | |
251 if (kind == ElementKind.FIELD || kind == ElementKind.METHOD) { | |
252 return "${element.enclosingElement.displayName}.${element.displayName}"; | |
253 } else { | |
254 return element.displayName; | |
255 } | |
256 break; | |
257 } | |
258 } | |
259 | |
260 /** | |
261 * @return the [ExecutableElement] of the enclosing executable [AstNode]. | |
262 */ | |
263 static ExecutableElement getEnclosingExecutableElement(AstNode node) { | |
264 while (node != null) { | |
265 if (node is FunctionDeclaration) { | |
266 return node.element; | |
267 } | |
268 if (node is ConstructorDeclaration) { | |
269 return node.element; | |
270 } | |
271 if (node is MethodDeclaration) { | |
272 return node.element; | |
273 } | |
274 node = node.parent; | |
275 } | |
276 return null; | |
277 } | |
278 | |
279 /** | |
280 * @return the enclosing executable [AstNode]. | |
281 */ | |
282 static AstNode getEnclosingExecutableNode(AstNode node) { | |
283 while (node != null) { | |
284 if (node is FunctionDeclaration) { | |
285 return node; | |
286 } | |
287 if (node is ConstructorDeclaration) { | |
288 return node; | |
289 } | |
290 if (node is MethodDeclaration) { | |
291 return node; | |
292 } | |
293 node = node.parent; | |
294 } | |
295 return null; | |
296 } | |
297 | |
298 /** | |
299 * @return [Element] exported from the given [LibraryElement]. | |
300 */ | |
301 static Element getExportedElement(LibraryElement library, String name) { | |
302 if (library == null) { | |
303 return null; | |
304 } | |
305 return getExportNamespace2(library)[name]; | |
306 } | |
307 | |
308 /** | |
309 * TODO(scheglov) may be replace with some API for this | |
310 * | |
311 * @return the namespace of the given [ExportElement]. | |
312 */ | |
313 static Map<String, Element> getExportNamespace(ExportElement exp) { | |
314 Namespace namespace = new NamespaceBuilder().createExportNamespaceForDirecti
ve(exp); | |
315 return namespace.definedNames; | |
316 } | |
317 | |
318 /** | |
319 * TODO(scheglov) may be replace with some API for this | |
320 * | |
321 * @return the export namespace of the given [LibraryElement]. | |
322 */ | |
323 static Map<String, Element> getExportNamespace2(LibraryElement library) { | |
324 Namespace namespace = new NamespaceBuilder().createExportNamespaceForLibrary
(library); | |
325 return namespace.definedNames; | |
326 } | |
327 | |
328 /** | |
329 * @return [getExpressionPrecedence] for parent node, or `0` if parent node | |
330 * is [ParenthesizedExpression]. The reason is that `(expr)` is always | |
331 * executed after `expr`. | |
332 */ | |
333 static int getExpressionParentPrecedence(AstNode node) { | |
334 AstNode parent = node.parent; | |
335 if (parent is ParenthesizedExpression) { | |
336 return 0; | |
337 } | |
338 return getExpressionPrecedence(parent); | |
339 } | |
340 | |
341 /** | |
342 * @return the precedence of the given node - result of [Expression#getPrecede
nce] if an | |
343 * [Expression], negative otherwise. | |
344 */ | |
345 static int getExpressionPrecedence(AstNode node) { | |
346 if (node is Expression) { | |
347 return node.precedence; | |
348 } | |
349 return -1000; | |
350 } | |
351 | |
352 /** | |
353 * TODO(scheglov) may be replace with some API for this | |
354 * | |
355 * @return the namespace of the given [ImportElement]. | |
356 */ | |
357 static Map<String, Element> getImportNamespace(ImportElement imp) { | |
358 Namespace namespace = new NamespaceBuilder().createImportNamespaceForDirecti
ve(imp); | |
359 return namespace.definedNames; | |
360 } | |
361 | |
362 /** | |
363 * @return all [CompilationUnitElement] the given [LibraryElement] consists of
. | |
364 */ | |
365 static List<CompilationUnitElement> getLibraryUnits(LibraryElement library) { | |
366 List<CompilationUnitElement> units = []; | |
367 units.add(library.definingCompilationUnit); | |
368 units.addAll(library.parts); | |
369 return units; | |
370 } | |
371 | |
372 /** | |
373 * @return the line prefix from the given source, i.e. basically just whitespa
ce prefix of the | |
374 * given [String]. | |
375 */ | |
376 static String getLinesPrefix(String lines) { | |
377 int index = 0; | |
378 while (index < lines.length) { | |
379 int c = lines.codeUnitAt(index); | |
380 if (!Character.isWhitespace(c)) { | |
381 break; | |
382 } | |
383 index++; | |
384 } | |
385 return lines.substring(0, index); | |
386 } | |
387 | |
388 /** | |
389 * @return the [LocalVariableElement] or [ParameterElement] if given | |
390 * [SimpleIdentifier] is the reference to local variable or parameter,
or | |
391 * <code>null</code> in the other case. | |
392 */ | |
393 static VariableElement getLocalOrParameterVariableElement(SimpleIdentifier nod
e) { | |
394 Element element = node.staticElement; | |
395 if (element is LocalVariableElement) { | |
396 return element; | |
397 } | |
398 if (element is ParameterElement) { | |
399 return element; | |
400 } | |
401 return null; | |
402 } | |
403 | |
404 /** | |
405 * @return the [LocalVariableElement] if given [SimpleIdentifier] is the refer
ence to | |
406 * local variable, or <code>null</code> in the other case. | |
407 */ | |
408 static LocalVariableElement getLocalVariableElement(SimpleIdentifier node) { | |
409 Element element = node.staticElement; | |
410 if (element is LocalVariableElement) { | |
411 return element; | |
412 } | |
413 return null; | |
414 } | |
415 | |
416 /** | |
417 * @return the nearest common ancestor [AstNode] of the given [AstNode]s. | |
418 */ | |
419 static AstNode getNearestCommonAncestor(List<AstNode> nodes) { | |
420 // may be no nodes | |
421 if (nodes.isEmpty) { | |
422 return null; | |
423 } | |
424 // prepare parents | |
425 List<List<AstNode>> parents = []; | |
426 for (AstNode node in nodes) { | |
427 parents.add(getParents(node)); | |
428 } | |
429 // find min length | |
430 int minLength = 2147483647; | |
431 for (List<AstNode> parentList in parents) { | |
432 minLength = Math.min(minLength, parentList.length); | |
433 } | |
434 // find deepest parent | |
435 int i = 0; | |
436 for (; i < minLength; i++) { | |
437 if (!allListsEqual(parents, i)) { | |
438 break; | |
439 } | |
440 } | |
441 return parents[0][i - 1]; | |
442 } | |
443 | |
444 /** | |
445 * @return the [Expression] qualified if given node is name part of a [Propert
yAccess] | |
446 * or [PrefixedIdentifier]. May be <code>null</code>. | |
447 */ | |
448 static Expression getNodeQualifier(SimpleIdentifier node) { | |
449 AstNode parent = node.parent; | |
450 if (parent is PropertyAccess) { | |
451 PropertyAccess propertyAccess = parent; | |
452 if (identical(propertyAccess.propertyName, node)) { | |
453 return propertyAccess.target; | |
454 } | |
455 } | |
456 if (parent is PrefixedIdentifier) { | |
457 PrefixedIdentifier prefixed = parent; | |
458 if (identical(prefixed.identifier, node)) { | |
459 return prefixed.prefix; | |
460 } | |
461 } | |
462 return null; | |
463 } | |
464 | |
465 /** | |
466 * @return the [ParameterElement] if given [SimpleIdentifier] is the reference
to | |
467 * parameter, or <code>null</code> in the other case. | |
468 */ | |
469 static ParameterElement getParameterElement(SimpleIdentifier node) { | |
470 Element element = node.staticElement; | |
471 if (element is ParameterElement) { | |
472 return element; | |
473 } | |
474 return null; | |
475 } | |
476 | |
477 /** | |
478 * @return the precedence of the given [Expression] parent. May be `-1` no ope
rator. | |
479 * @see #getPrecedence(Expression) | |
480 */ | |
481 static int getParentPrecedence(Expression expression) { | |
482 AstNode parent = expression.parent; | |
483 if (parent is Expression) { | |
484 return getPrecedence(parent); | |
485 } | |
486 return -1; | |
487 } | |
488 | |
489 /** | |
490 * @return parent [AstNode]s from [CompilationUnit] (at index "0") to the give
n one. | |
491 */ | |
492 static List<AstNode> getParents(AstNode node) { | |
493 // prepare number of parents | |
494 int numParents = 0; | |
495 { | |
496 AstNode current = node.parent; | |
497 while (current != null) { | |
498 numParents++; | |
499 current = current.parent; | |
500 } | |
501 } | |
502 // fill array of parents | |
503 List<AstNode> parents = new List<AstNode>(numParents); | |
504 AstNode current = node.parent; | |
505 int index = numParents; | |
506 while (current != null) { | |
507 parents[--index] = current; | |
508 current = current.parent; | |
509 } | |
510 return JavaArrays.asList(parents); | |
511 } | |
512 | |
513 /** | |
514 * @return the precedence of the given [Expression] operator. May be | |
515 * `Integer#MAX_VALUE` if not an operator. | |
516 */ | |
517 static int getPrecedence(Expression expression) { | |
518 if (expression is BinaryExpression) { | |
519 BinaryExpression binaryExpression = expression; | |
520 return binaryExpression.operator.type.precedence; | |
521 } | |
522 if (expression is PrefixExpression) { | |
523 PrefixExpression prefixExpression = expression; | |
524 return prefixExpression.operator.type.precedence; | |
525 } | |
526 if (expression is PostfixExpression) { | |
527 PostfixExpression postfixExpression = expression; | |
528 return postfixExpression.operator.type.precedence; | |
529 } | |
530 return 2147483647; | |
531 } | |
532 | |
533 /** | |
534 * @return the [PropertyAccessorElement] if given [SimpleIdentifier] is the re
ference | |
535 * to property, or <code>null</code> in the other case. | |
536 */ | |
537 static PropertyAccessorElement getPropertyAccessorElement(SimpleIdentifier nod
e) { | |
538 Element element = node.staticElement; | |
539 if (element is PropertyAccessorElement) { | |
540 return element; | |
541 } | |
542 return null; | |
543 } | |
544 | |
545 /** | |
546 * If given [AstNode] is name of qualified property extraction, returns target
from which | |
547 * this property is extracted. Otherwise `null`. | |
548 */ | |
549 static Expression getQualifiedPropertyTarget(AstNode node) { | |
550 AstNode parent = node.parent; | |
551 if (parent is PrefixedIdentifier) { | |
552 PrefixedIdentifier prefixed = parent; | |
553 if (identical(prefixed.identifier, node)) { | |
554 return parent.prefix; | |
555 } | |
556 } | |
557 if (parent is PropertyAccess) { | |
558 PropertyAccess access = parent; | |
559 if (identical(access.propertyName, node)) { | |
560 return access.realTarget; | |
561 } | |
562 } | |
563 return null; | |
564 } | |
565 | |
566 /** | |
567 * Returns the name of the file which corresponds to the name of the class acc
ording to the style | |
568 * guide. However class does not have to be in this file. | |
569 */ | |
570 static String getRecommentedFileNameForClass(String className) { | |
571 int len = className.length; | |
572 JavaStringBuilder sb = new JavaStringBuilder(); | |
573 bool prevWasUpper = false; | |
574 for (int i = 0; i < len; i++) { | |
575 int c = className.codeUnitAt(i); | |
576 if (Character.isUpperCase(c)) { | |
577 bool nextIsUpper = i < len - 1 && Character.isUpperCase(className.codeUn
itAt(i + 1)); | |
578 if (i == 0) { | |
579 } else if (prevWasUpper) { | |
580 // HTTPServer | |
581 // ^ | |
582 if (!nextIsUpper) { | |
583 sb.appendChar(0x5F); | |
584 } | |
585 } else { | |
586 // HttpServer | |
587 // ^ | |
588 sb.appendChar(0x5F); | |
589 } | |
590 prevWasUpper = true; | |
591 c = Character.toLowerCase(c); | |
592 } else { | |
593 prevWasUpper = false; | |
594 } | |
595 sb.appendChar(c); | |
596 } | |
597 sb.append(".dart"); | |
598 String fileName = sb.toString(); | |
599 return fileName; | |
600 } | |
601 | |
602 /** | |
603 * @return given [Statement] if not [Block], first child [Statement] if | |
604 * [Block], or <code>null</code> if more than one child. | |
605 */ | |
606 static Statement getSingleStatement(Statement statement) { | |
607 if (statement is Block) { | |
608 List<Statement> blockStatements = statement.statements; | |
609 if (blockStatements.length != 1) { | |
610 return null; | |
611 } | |
612 return blockStatements[0]; | |
613 } | |
614 return statement; | |
615 } | |
616 | |
617 /** | |
618 * @return the [String] content of the given [Source]. | |
619 */ | |
620 static String getSourceContent(AnalysisContext context, Source source) => cont
ext.getContents(source).data.toString(); | |
621 | |
622 /** | |
623 * @return given [Statement] if not [Block], all children [Statement]s if | |
624 * [Block]. | |
625 */ | |
626 static List<Statement> getStatements(Statement statement) { | |
627 if (statement is Block) { | |
628 return statement.statements; | |
629 } | |
630 return []; | |
631 } | |
632 | |
633 /** | |
634 * @return all top-level elements declared in the given [LibraryElement]. | |
635 */ | |
636 static List<Element> getTopLevelElements(LibraryElement library) { | |
637 List<Element> elements = []; | |
638 List<CompilationUnitElement> units = getLibraryUnits(library); | |
639 for (CompilationUnitElement unit in units) { | |
640 elements.addAll(unit.functions); | |
641 elements.addAll(unit.functionTypeAliases); | |
642 elements.addAll(unit.types); | |
643 elements.addAll(unit.topLevelVariables); | |
644 } | |
645 return elements; | |
646 } | |
647 | |
648 /** | |
649 * @return the possible names for variable with initializer of the given [Stri
ngLiteral]. | |
650 */ | |
651 static List<String> getVariableNameSuggestions(String text, Set<String> exclud
ed) { | |
652 // filter out everything except of letters and white spaces | |
653 { | |
654 JavaStringBuilder sb = new JavaStringBuilder(); | |
655 for (int i = 0; i < text.length; i++) { | |
656 int c = text.codeUnitAt(i); | |
657 if (Character.isLetter(c) || Character.isWhitespace(c)) { | |
658 sb.appendChar(c); | |
659 } | |
660 } | |
661 text = sb.toString(); | |
662 } | |
663 // make single camel-case text | |
664 { | |
665 List<String> words = StringUtils.split(text); | |
666 JavaStringBuilder sb = new JavaStringBuilder(); | |
667 for (int i = 0; i < words.length; i++) { | |
668 String word = words[i]; | |
669 if (i > 0) { | |
670 word = StringUtils.capitalize(word); | |
671 } | |
672 sb.append(word); | |
673 } | |
674 text = sb.toString(); | |
675 } | |
676 // split camel-case into separate suggested names | |
677 Set<String> res = new LinkedHashSet(); | |
678 _addAll(excluded, res, _getVariableNameSuggestions(text)); | |
679 return new List.from(res); | |
680 } | |
681 | |
682 /** | |
683 * @return the possible names for variable with given expected type and expres
sion. | |
684 */ | |
685 static List<String> getVariableNameSuggestions2(DartType expectedType, Express
ion assignedExpression, Set<String> excluded) { | |
686 Set<String> res = new LinkedHashSet(); | |
687 // use expression | |
688 if (assignedExpression != null) { | |
689 String nameFromExpression = _getBaseNameFromExpression(assignedExpression)
; | |
690 if (nameFromExpression != null) { | |
691 nameFromExpression = StringUtils.removeStart(nameFromExpression, "_"); | |
692 _addAll(excluded, res, _getVariableNameSuggestions(nameFromExpression)); | |
693 } | |
694 String nameFromParent = _getBaseNameFromLocationInParent(assignedExpressio
n); | |
695 if (nameFromParent != null) { | |
696 _addAll(excluded, res, _getVariableNameSuggestions(nameFromParent)); | |
697 } | |
698 } | |
699 // use type | |
700 if (expectedType != null && !expectedType.isDynamic) { | |
701 String typeName = expectedType.name; | |
702 if ("int" == typeName) { | |
703 _addSingleCharacterName(excluded, res, 0x69); | |
704 } else if ("double" == typeName) { | |
705 _addSingleCharacterName(excluded, res, 0x64); | |
706 } else if ("String" == typeName) { | |
707 _addSingleCharacterName(excluded, res, 0x73); | |
708 } else { | |
709 _addAll(excluded, res, _getVariableNameSuggestions(typeName)); | |
710 } | |
711 res.remove(typeName); | |
712 } | |
713 // done | |
714 return new List.from(res); | |
715 } | |
716 | |
717 /** | |
718 * @return `true` if the given [Element#getDisplayName] equals to the given na
me. | |
719 */ | |
720 static bool hasDisplayName(Element element, String name) { | |
721 if (element == null) { | |
722 return false; | |
723 } | |
724 String elementDisplayName = element.displayName; | |
725 return StringUtils.equals(elementDisplayName, name); | |
726 } | |
727 | |
728 /** | |
729 * @return `true` if the given [Element#getName] equals to the given name. | |
730 */ | |
731 static bool hasName(Element element, String name) { | |
732 if (element == null) { | |
733 return false; | |
734 } | |
735 String elementName = element.name; | |
736 return StringUtils.equals(elementName, name); | |
737 } | |
738 | |
739 /** | |
740 * @return `true` if the given [SimpleIdentifier] is the name of the | |
741 * [NamedExpression]. | |
742 */ | |
743 static bool isNamedExpressionName(SimpleIdentifier node) { | |
744 AstNode parent = node.parent; | |
745 if (parent is Label) { | |
746 Label label = parent; | |
747 if (identical(label.label, node)) { | |
748 AstNode parent2 = label.parent; | |
749 if (parent2 is NamedExpression) { | |
750 return identical(parent2.name, label); | |
751 } | |
752 } | |
753 } | |
754 return false; | |
755 } | |
756 | |
757 /** | |
758 * Adds "toAdd" items which are not excluded. | |
759 */ | |
760 static void _addAll(Set<String> excluded, Set<String> result, Iterable<String>
toAdd) { | |
761 for (String item in toAdd) { | |
762 // add name based on "item", but not "excluded" | |
763 for (int suffix = 1;; suffix++) { | |
764 // prepare name, just "item" or "item2", "item3", etc | |
765 String name = item; | |
766 if (suffix > 1) { | |
767 name += suffix.toString(); | |
768 } | |
769 // add once found not excluded | |
770 if (!excluded.contains(name)) { | |
771 result.add(name); | |
772 break; | |
773 } | |
774 } | |
775 } | |
776 } | |
777 | |
778 /** | |
779 * Add to "result" then given "c" or the first ASCII character after it. | |
780 */ | |
781 static void _addSingleCharacterName(Set<String> excluded, Set<String> result,
int c) { | |
782 while (c < 0x7A) { | |
783 String name = new String.fromCharCode(c); | |
784 // may be done | |
785 if (!excluded.contains(name)) { | |
786 result.add(name); | |
787 break; | |
788 } | |
789 // next character | |
790 c = (c + 1); | |
791 } | |
792 } | |
793 | |
794 static String _getBaseNameFromExpression(Expression expression) { | |
795 String name = null; | |
796 // e as Type | |
797 if (expression is AsExpression) { | |
798 AsExpression asExpression = expression as AsExpression; | |
799 expression = asExpression.expression; | |
800 } | |
801 // analyze expressions | |
802 if (expression is SimpleIdentifier) { | |
803 SimpleIdentifier node = expression; | |
804 return node.name; | |
805 } else if (expression is PrefixedIdentifier) { | |
806 PrefixedIdentifier node = expression; | |
807 return node.identifier.name; | |
808 } else if (expression is MethodInvocation) { | |
809 name = expression.methodName.name; | |
810 } else if (expression is InstanceCreationExpression) { | |
811 InstanceCreationExpression creation = expression; | |
812 ConstructorName constructorName = creation.constructorName; | |
813 TypeName typeName = constructorName.type; | |
814 if (typeName != null) { | |
815 Identifier typeNameIdentifier = typeName.name; | |
816 // new ClassName() | |
817 if (typeNameIdentifier is SimpleIdentifier) { | |
818 return typeNameIdentifier.name; | |
819 } | |
820 // new prefix.name(); | |
821 if (typeNameIdentifier is PrefixedIdentifier) { | |
822 PrefixedIdentifier prefixed = typeNameIdentifier; | |
823 // new prefix.ClassName() | |
824 if (prefixed.prefix.staticElement is PrefixElement) { | |
825 return prefixed.identifier.name; | |
826 } | |
827 // new ClassName.constructorName() | |
828 return prefixed.prefix.name; | |
829 } | |
830 } | |
831 } | |
832 // strip known prefixes | |
833 if (name != null) { | |
834 for (int i = 0; i < _KNOWN_METHOD_NAME_PREFIXES.length; i++) { | |
835 String curr = _KNOWN_METHOD_NAME_PREFIXES[i]; | |
836 if (name.startsWith(curr)) { | |
837 if (name == curr) { | |
838 return null; | |
839 } else if (Character.isUpperCase(name.codeUnitAt(curr.length))) { | |
840 return name.substring(curr.length); | |
841 } | |
842 } | |
843 } | |
844 } | |
845 // done | |
846 return name; | |
847 } | |
848 | |
849 static String _getBaseNameFromLocationInParent(Expression expression) { | |
850 // value in named expression | |
851 if (expression.parent is NamedExpression) { | |
852 NamedExpression namedExpression = expression.parent as NamedExpression; | |
853 if (identical(namedExpression.expression, expression)) { | |
854 return namedExpression.name.label.name; | |
855 } | |
856 } | |
857 // positional argument | |
858 { | |
859 ParameterElement parameter = expression.propagatedParameterElement; | |
860 if (parameter == null) { | |
861 parameter = expression.staticParameterElement; | |
862 } | |
863 if (parameter != null) { | |
864 return parameter.displayName; | |
865 } | |
866 } | |
867 // unknown | |
868 return null; | |
869 } | |
870 | |
871 /** | |
872 * @return [Expression]s from <code>operands</code> which are completely cover
ed by given | |
873 * [SourceRange]. Range should start and end between given [Expression
]s. | |
874 */ | |
875 static List<Expression> _getOperandsForSourceRange(List<Expression> operands,
SourceRange range) { | |
876 assert(!operands.isEmpty); | |
877 List<Expression> subOperands = []; | |
878 // track range enter/exit | |
879 bool entered = false; | |
880 bool exited = false; | |
881 // may be range starts before or on first operand | |
882 if (range.offset <= operands[0].offset) { | |
883 entered = true; | |
884 } | |
885 // iterate over gaps between operands | |
886 for (int i = 0; i < operands.length - 1; i++) { | |
887 Expression operand = operands[i]; | |
888 Expression nextOperand = operands[i + 1]; | |
889 SourceRange inclusiveGap = SourceRangeFactory.rangeEndStart(operand, nextO
perand).getMoveEnd(1); | |
890 // add operand, if already entered range | |
891 if (entered) { | |
892 subOperands.add(operand); | |
893 // may be last operand in range | |
894 if (range.endsIn(inclusiveGap)) { | |
895 exited = true; | |
896 } | |
897 } else { | |
898 // may be first operand in range | |
899 if (range.startsIn(inclusiveGap)) { | |
900 entered = true; | |
901 } | |
902 } | |
903 } | |
904 // check if last operand is in range | |
905 Expression lastGroupMember = operands[operands.length - 1]; | |
906 if (range.end == lastGroupMember.end) { | |
907 subOperands.add(lastGroupMember); | |
908 exited = true; | |
909 } | |
910 // we expect that range covers only given operands | |
911 if (!exited) { | |
912 return []; | |
913 } | |
914 // done | |
915 return subOperands; | |
916 } | |
917 | |
918 /** | |
919 * @return all operands of the given [BinaryExpression] and its children with
the same | |
920 * operator. | |
921 */ | |
922 static List<Expression> _getOperandsInOrderFor(BinaryExpression groupRoot) { | |
923 List<Expression> operands = []; | |
924 TokenType groupOperatorType = groupRoot.operator.type; | |
925 groupRoot.accept(new GeneralizingAstVisitor_CorrectionUtils_getOperandsInOrd
erFor(groupOperatorType, operands)); | |
926 return operands; | |
927 } | |
928 | |
929 /** | |
930 * @return all variants of names by removing leading words by one. | |
931 */ | |
932 static List<String> _getVariableNameSuggestions(String name) { | |
933 List<String> result = []; | |
934 List<String> parts = name.split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z]
)"); | |
935 for (int i = 0; i < parts.length; i++) { | |
936 String suggestion = "${parts[i].toLowerCase()}${StringUtils.join(parts, ""
, i + 1, parts.length)}"; | |
937 result.add(suggestion); | |
938 } | |
939 return result; | |
940 } | |
941 | |
942 /** | |
943 * Adds enclosing parenthesis if the precedence of the [InvertedCondition] if
less than the | |
944 * precedence of the expression we are going it to use in. | |
945 */ | |
946 static String _parenthesizeIfRequired(CorrectionUtils_InvertedCondition expr,
int newOperatorPrecedence) { | |
947 if (expr._precedence < newOperatorPrecedence) { | |
948 return "(${expr._source})"; | |
949 } | |
950 return expr._source; | |
951 } | |
952 | |
953 final CompilationUnit unit; | |
954 | |
955 LibraryElement _library; | |
956 | |
957 String _buffer; | |
958 | |
959 String _endOfLine; | |
960 | |
961 CorrectionUtils(this.unit) { | |
962 CompilationUnitElement element = unit.element; | |
963 this._library = element.library; | |
964 this._buffer = getSourceContent(element.context, element.source); | |
965 } | |
966 | |
967 /** | |
968 * @return the source of the given [SourceRange] with indentation changed from
"oldIndent" | |
969 * to "newIndent", keeping indentation of the lines relative to each o
ther. | |
970 */ | |
971 Edit createIndentEdit(SourceRange range, String oldIndent, String newIndent) { | |
972 String newSource = getIndentSource(range, oldIndent, newIndent); | |
973 return new Edit(range.offset, range.length, newSource); | |
974 } | |
975 | |
976 /** | |
977 * @return the [AstNode] that encloses the given offset. | |
978 */ | |
979 AstNode findNode(int offset) => new NodeLocator.con1(offset).searchWithin(unit
); | |
980 | |
981 /** | |
982 * TODO(scheglov) replace with nodes once there will be [CompilationUnit#getCo
mments]. | |
983 * | |
984 * @return the [SourceRange]s of all comments in [CompilationUnit]. | |
985 */ | |
986 List<SourceRange> get commentRanges { | |
987 List<SourceRange> ranges = []; | |
988 Token token = unit.beginToken; | |
989 while (token != null && token.type != TokenType.EOF) { | |
990 Token commentToken = token.precedingComments; | |
991 while (commentToken != null) { | |
992 ranges.add(SourceRangeFactory.rangeToken(commentToken)); | |
993 commentToken = commentToken.next; | |
994 } | |
995 token = token.next; | |
996 } | |
997 return ranges; | |
998 } | |
999 | |
1000 /** | |
1001 * @return the EOL to use for this [CompilationUnit]. | |
1002 */ | |
1003 String get endOfLine { | |
1004 if (_endOfLine == null) { | |
1005 if (_buffer.contains("\r\n")) { | |
1006 _endOfLine = "\r\n"; | |
1007 } else { | |
1008 _endOfLine = "\n"; | |
1009 } | |
1010 } | |
1011 return _endOfLine; | |
1012 } | |
1013 | |
1014 /** | |
1015 * @return the default indentation with given level. | |
1016 */ | |
1017 String getIndent(int level) => StringUtils.repeat(" ", level); | |
1018 | |
1019 /** | |
1020 * @return the source of the given [SourceRange] with indentation changed from
"oldIndent" | |
1021 * to "newIndent", keeping indentation of the lines relative to each o
ther. | |
1022 */ | |
1023 String getIndentSource(SourceRange range, String oldIndent, String newIndent)
{ | |
1024 String oldSource = getText3(range); | |
1025 return getIndentSource3(oldSource, oldIndent, newIndent); | |
1026 } | |
1027 | |
1028 /** | |
1029 * Indents given source left or right. | |
1030 * | |
1031 * @return the source with changed indentation. | |
1032 */ | |
1033 String getIndentSource2(String source, bool right) { | |
1034 JavaStringBuilder sb = new JavaStringBuilder(); | |
1035 String indent = getIndent(1); | |
1036 String eol = endOfLine; | |
1037 List<String> lines = StringUtils.splitByWholeSeparatorPreserveAllTokens(sour
ce, eol); | |
1038 for (int i = 0; i < lines.length; i++) { | |
1039 String line = lines[i]; | |
1040 // last line, stop if empty | |
1041 if (i == lines.length - 1 && StringUtils.isEmpty(line)) { | |
1042 break; | |
1043 } | |
1044 // update line | |
1045 if (right) { | |
1046 line = "${indent}${line}"; | |
1047 } else { | |
1048 line = StringUtils.removeStart(line, indent); | |
1049 } | |
1050 // append line | |
1051 sb.append(line); | |
1052 sb.append(eol); | |
1053 } | |
1054 return sb.toString(); | |
1055 } | |
1056 | |
1057 /** | |
1058 * @return the source with indentation changed from "oldIndent" to "newIndent"
, keeping | |
1059 * indentation of the lines relative to each other. | |
1060 */ | |
1061 String getIndentSource3(String source, String oldIndent, String newIndent) { | |
1062 // prepare STRING token ranges | |
1063 List<SourceRange> lineRanges = []; | |
1064 for (Token token in TokenUtils.getTokens(source)) { | |
1065 if (token.type == TokenType.STRING) { | |
1066 lineRanges.add(SourceRangeFactory.rangeToken(token)); | |
1067 } | |
1068 } | |
1069 // re-indent lines | |
1070 JavaStringBuilder sb = new JavaStringBuilder(); | |
1071 String eol = endOfLine; | |
1072 List<String> lines = StringUtils.splitByWholeSeparatorPreserveAllTokens(sour
ce, eol); | |
1073 int lineOffset = 0; | |
1074 for (int i = 0; i < lines.length; i++) { | |
1075 String line = lines[i]; | |
1076 // last line, stop if empty | |
1077 if (i == lines.length - 1 && StringUtils.isEmpty(line)) { | |
1078 break; | |
1079 } | |
1080 // check if "offset" is in one of the String ranges | |
1081 bool inString = false; | |
1082 for (SourceRange lineRange in lineRanges) { | |
1083 inString = javaBooleanOr(inString, lineOffset > lineRange.offset && line
Offset < lineRange.end); | |
1084 if (lineOffset > lineRange.end) { | |
1085 break; | |
1086 } | |
1087 } | |
1088 lineOffset += line.length + eol.length; | |
1089 // update line indent | |
1090 if (!inString) { | |
1091 line = "${newIndent}${StringUtils.removeStart(line, oldIndent)}"; | |
1092 } | |
1093 // append line | |
1094 sb.append(line); | |
1095 sb.append(eol); | |
1096 } | |
1097 return sb.toString(); | |
1098 } | |
1099 | |
1100 /** | |
1101 * @return [InsertDesc], description where to insert new library-related direc
tive. | |
1102 */ | |
1103 CorrectionUtils_InsertDesc get insertDescImport { | |
1104 // analyze directives | |
1105 Directive prevDirective = null; | |
1106 for (Directive directive in unit.directives) { | |
1107 if (directive is LibraryDirective || directive is ImportDirective || direc
tive is ExportDirective) { | |
1108 prevDirective = directive; | |
1109 } | |
1110 } | |
1111 // insert after last library-related directive | |
1112 if (prevDirective != null) { | |
1113 CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc(); | |
1114 result.offset = prevDirective.end; | |
1115 String eol = endOfLine; | |
1116 if (prevDirective is LibraryDirective) { | |
1117 result.prefix = "${eol}${eol}"; | |
1118 } else { | |
1119 result.prefix = eol; | |
1120 } | |
1121 return result; | |
1122 } | |
1123 // no directives, use "top" location | |
1124 return insertDescTop; | |
1125 } | |
1126 | |
1127 /** | |
1128 * @return [InsertDesc], description where to insert new 'part 'directive. | |
1129 */ | |
1130 CorrectionUtils_InsertDesc get insertDescPart { | |
1131 // analyze directives | |
1132 Directive prevDirective = null; | |
1133 for (Directive directive in unit.directives) { | |
1134 prevDirective = directive; | |
1135 } | |
1136 // insert after last directive | |
1137 if (prevDirective != null) { | |
1138 CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc(); | |
1139 result.offset = prevDirective.end; | |
1140 String eol = endOfLine; | |
1141 if (prevDirective is PartDirective) { | |
1142 result.prefix = eol; | |
1143 } else { | |
1144 result.prefix = "${eol}${eol}"; | |
1145 } | |
1146 return result; | |
1147 } | |
1148 // no directives, use "top" location | |
1149 return insertDescTop; | |
1150 } | |
1151 | |
1152 /** | |
1153 * @return [InsertDesc], description where to insert new directive or top-leve
l declaration | |
1154 * at the top of file. | |
1155 */ | |
1156 CorrectionUtils_InsertDesc get insertDescTop { | |
1157 // skip leading line comments | |
1158 int offset = 0; | |
1159 bool insertEmptyLineBefore = false; | |
1160 bool insertEmptyLineAfter = false; | |
1161 String source = text; | |
1162 // skip hash-bang | |
1163 if (offset < source.length - 2) { | |
1164 String linePrefix = getText2(offset, 2); | |
1165 if (linePrefix == "#!") { | |
1166 insertEmptyLineBefore = true; | |
1167 offset = getLineNext(offset); | |
1168 // skip empty lines to first line comment | |
1169 int emptyOffset = offset; | |
1170 while (emptyOffset < source.length - 2) { | |
1171 int nextLineOffset = getLineNext(emptyOffset); | |
1172 String line = source.substring(emptyOffset, nextLineOffset); | |
1173 if (line.trim().isEmpty) { | |
1174 emptyOffset = nextLineOffset; | |
1175 continue; | |
1176 } else if (line.startsWith("//")) { | |
1177 offset = emptyOffset; | |
1178 break; | |
1179 } else { | |
1180 break; | |
1181 } | |
1182 } | |
1183 } | |
1184 } | |
1185 // skip line comments | |
1186 while (offset < source.length - 2) { | |
1187 String linePrefix = getText2(offset, 2); | |
1188 if (linePrefix == "//") { | |
1189 insertEmptyLineBefore = true; | |
1190 offset = getLineNext(offset); | |
1191 } else { | |
1192 break; | |
1193 } | |
1194 } | |
1195 // determine if empty line is required after | |
1196 int nextLineOffset = getLineNext(offset); | |
1197 String insertLine = source.substring(offset, nextLineOffset); | |
1198 if (!insertLine.trim().isEmpty) { | |
1199 insertEmptyLineAfter = true; | |
1200 } | |
1201 // fill InsertDesc | |
1202 CorrectionUtils_InsertDesc desc = new CorrectionUtils_InsertDesc(); | |
1203 desc.offset = offset; | |
1204 if (insertEmptyLineBefore) { | |
1205 desc.prefix = endOfLine; | |
1206 } | |
1207 if (insertEmptyLineAfter) { | |
1208 desc.suffix = endOfLine; | |
1209 } | |
1210 return desc; | |
1211 } | |
1212 | |
1213 /** | |
1214 * Skips whitespace characters and single EOL on the right from the given posi
tion. If from | |
1215 * statement or method end, then this is in the most cases start of the next l
ine. | |
1216 */ | |
1217 int getLineContentEnd(int index) { | |
1218 int length = _buffer.length; | |
1219 // skip whitespace characters | |
1220 while (index < length) { | |
1221 int c = _buffer.codeUnitAt(index); | |
1222 if (!Character.isWhitespace(c) || c == 0xD || c == 0xA) { | |
1223 break; | |
1224 } | |
1225 index++; | |
1226 } | |
1227 // skip single \r | |
1228 if (index < length && _buffer.codeUnitAt(index) == 0xD) { | |
1229 index++; | |
1230 } | |
1231 // skip single \n | |
1232 if (index < length && _buffer.codeUnitAt(index) == 0xA) { | |
1233 index++; | |
1234 } | |
1235 // done | |
1236 return index; | |
1237 } | |
1238 | |
1239 /** | |
1240 * @return the index of the last space or tab on the left from the given one,
if from statement or | |
1241 * method start, then this is in most cases start of the line. | |
1242 */ | |
1243 int getLineContentStart(int index) { | |
1244 while (index > 0) { | |
1245 int c = _buffer.codeUnitAt(index - 1); | |
1246 if (c != 0x20 && c != 0x9) { | |
1247 break; | |
1248 } | |
1249 index--; | |
1250 } | |
1251 return index; | |
1252 } | |
1253 | |
1254 /** | |
1255 * @return the start index of the next line after the line which contains give
n index. | |
1256 */ | |
1257 int getLineNext(int index) { | |
1258 int length = _buffer.length; | |
1259 // skip to the end of the line | |
1260 while (index < length) { | |
1261 int c = _buffer.codeUnitAt(index); | |
1262 if (c == 0xD || c == 0xA) { | |
1263 break; | |
1264 } | |
1265 index++; | |
1266 } | |
1267 // skip single \r | |
1268 if (index < length && _buffer.codeUnitAt(index) == 0xD) { | |
1269 index++; | |
1270 } | |
1271 // skip single \n | |
1272 if (index < length && _buffer.codeUnitAt(index) == 0xA) { | |
1273 index++; | |
1274 } | |
1275 // done | |
1276 return index; | |
1277 } | |
1278 | |
1279 /** | |
1280 * @return the whitespace prefix of the line which contains given offset. | |
1281 */ | |
1282 String getLinePrefix(int index) { | |
1283 int lineStart = getLineThis(index); | |
1284 int length = _buffer.length; | |
1285 int lineNonWhitespace = lineStart; | |
1286 while (lineNonWhitespace < length) { | |
1287 int c = _buffer.codeUnitAt(lineNonWhitespace); | |
1288 if (c == 0xD || c == 0xA) { | |
1289 break; | |
1290 } | |
1291 if (!Character.isWhitespace(c)) { | |
1292 break; | |
1293 } | |
1294 lineNonWhitespace++; | |
1295 } | |
1296 return getText2(lineStart, lineNonWhitespace - lineStart); | |
1297 } | |
1298 | |
1299 /** | |
1300 * @return the [getLinesRange] for given [Statement]s. | |
1301 */ | |
1302 SourceRange getLinesRange(List<Statement> statements) { | |
1303 SourceRange range = SourceRangeFactory.rangeNodes(statements); | |
1304 return getLinesRange2(range); | |
1305 } | |
1306 | |
1307 /** | |
1308 * @return the [SourceRange] which starts at the start of the line of "offset"
and ends at | |
1309 * the start of the next line after "end" of the given [SourceRange],
i.e. basically | |
1310 * complete lines of the source for given [SourceRange]. | |
1311 */ | |
1312 SourceRange getLinesRange2(SourceRange range) { | |
1313 // start | |
1314 int startOffset = range.offset; | |
1315 int startLineOffset = getLineContentStart(startOffset); | |
1316 // end | |
1317 int endOffset = range.end; | |
1318 int afterEndLineOffset = getLineContentEnd(endOffset); | |
1319 // range | |
1320 return SourceRangeFactory.rangeStartEnd(startLineOffset, afterEndLineOffset)
; | |
1321 } | |
1322 | |
1323 /** | |
1324 * @return the [getLinesRange] for given [Statement]s. | |
1325 */ | |
1326 SourceRange getLinesRange3(List<Statement> statements) => getLinesRange([]); | |
1327 | |
1328 /** | |
1329 * @return the start index of the line which contains given index. | |
1330 */ | |
1331 int getLineThis(int index) { | |
1332 while (index > 0) { | |
1333 int c = _buffer.codeUnitAt(index - 1); | |
1334 if (c == 0xD || c == 0xA) { | |
1335 break; | |
1336 } | |
1337 index--; | |
1338 } | |
1339 return index; | |
1340 } | |
1341 | |
1342 /** | |
1343 * @return the line prefix consisting of spaces and tabs on the left from the
given | |
1344 * [AstNode]. | |
1345 */ | |
1346 String getNodePrefix(AstNode node) { | |
1347 int offset = node.offset; | |
1348 // function literal is special, it uses offset of enclosing line | |
1349 if (node is FunctionExpression) { | |
1350 return getLinePrefix(offset); | |
1351 } | |
1352 // use just prefix directly before node | |
1353 return getPrefix(offset); | |
1354 } | |
1355 | |
1356 /** | |
1357 * @return the index of the first non-whitespace character after given index. | |
1358 */ | |
1359 int getNonWhitespaceForward(int index) { | |
1360 int length = _buffer.length; | |
1361 // skip whitespace characters | |
1362 while (index < length) { | |
1363 int c = _buffer.codeUnitAt(index); | |
1364 if (!Character.isWhitespace(c)) { | |
1365 break; | |
1366 } | |
1367 index++; | |
1368 } | |
1369 // done | |
1370 return index; | |
1371 } | |
1372 | |
1373 /** | |
1374 * @return the source for the parameter with the given type and name. | |
1375 */ | |
1376 String getParameterSource(DartType type, String name) { | |
1377 // no type | |
1378 if (type == null || type.isDynamic) { | |
1379 return name; | |
1380 } | |
1381 // function type | |
1382 if (type is FunctionType) { | |
1383 FunctionType functionType = type; | |
1384 JavaStringBuilder sb = new JavaStringBuilder(); | |
1385 // return type | |
1386 DartType returnType = functionType.returnType; | |
1387 if (returnType != null && !returnType.isDynamic) { | |
1388 sb.append(getTypeSource2(returnType)); | |
1389 sb.appendChar(0x20); | |
1390 } | |
1391 // parameter name | |
1392 sb.append(name); | |
1393 // parameters | |
1394 sb.appendChar(0x28); | |
1395 List<ParameterElement> fParameters = functionType.parameters; | |
1396 for (int i = 0; i < fParameters.length; i++) { | |
1397 ParameterElement fParameter = fParameters[i]; | |
1398 if (i != 0) { | |
1399 sb.append(", "); | |
1400 } | |
1401 sb.append(getParameterSource(fParameter.type, fParameter.name)); | |
1402 } | |
1403 sb.appendChar(0x29); | |
1404 // done | |
1405 return sb.toString(); | |
1406 } | |
1407 // simple type | |
1408 return "${getTypeSource2(type)} ${name}"; | |
1409 } | |
1410 | |
1411 /** | |
1412 * @return the line prefix consisting of spaces and tabs on the left from the
given offset. | |
1413 */ | |
1414 String getPrefix(int endIndex) { | |
1415 int startIndex = getLineContentStart(endIndex); | |
1416 return _buffer.substring(startIndex, endIndex); | |
1417 } | |
1418 | |
1419 /** | |
1420 * @return the full text of unit. | |
1421 */ | |
1422 String get text => _buffer; | |
1423 | |
1424 /** | |
1425 * @return the given range of text from unit. | |
1426 */ | |
1427 String getText(AstNode node) => getText2(node.offset, node.length); | |
1428 | |
1429 /** | |
1430 * @return the given range of text from unit. | |
1431 */ | |
1432 String getText2(int offset, int length) => _buffer.substring(offset, offset +
length); | |
1433 | |
1434 /** | |
1435 * @return the given range of text from unit. | |
1436 */ | |
1437 String getText3(SourceRange range) => getText2(range.offset, range.length); | |
1438 | |
1439 /** | |
1440 * @return the actual type source of the given [Expression], may be `null` if
can not | |
1441 * be resolved, should be treated as <code>Dynamic</code>. | |
1442 */ | |
1443 String getTypeSource(Expression expression) { | |
1444 if (expression == null) { | |
1445 return null; | |
1446 } | |
1447 DartType type = expression.bestType; | |
1448 String typeSource = getTypeSource2(type); | |
1449 if ("dynamic" == typeSource) { | |
1450 return null; | |
1451 } | |
1452 return typeSource; | |
1453 } | |
1454 | |
1455 /** | |
1456 * @return the source to reference the given [Type] in this [CompilationUnit]. | |
1457 */ | |
1458 String getTypeSource2(DartType type) { | |
1459 JavaStringBuilder sb = new JavaStringBuilder(); | |
1460 // prepare element | |
1461 Element element = type.element; | |
1462 if (element == null) { | |
1463 String source = type.toString(); | |
1464 source = StringUtils.remove(source, "<dynamic>"); | |
1465 source = StringUtils.remove(source, "<dynamic, dynamic>"); | |
1466 return source; | |
1467 } | |
1468 // append prefix | |
1469 { | |
1470 ImportElement imp = _getImportElement(element); | |
1471 if (imp != null && imp.prefix != null) { | |
1472 sb.append(imp.prefix.displayName); | |
1473 sb.append("."); | |
1474 } | |
1475 } | |
1476 // append simple name | |
1477 String name = element.displayName; | |
1478 sb.append(name); | |
1479 // may be type arguments | |
1480 if (type is InterfaceType) { | |
1481 InterfaceType interfaceType = type; | |
1482 List<DartType> arguments = interfaceType.typeArguments; | |
1483 // check if has arguments | |
1484 bool hasArguments = false; | |
1485 for (DartType argument in arguments) { | |
1486 if (!argument.isDynamic) { | |
1487 hasArguments = true; | |
1488 break; | |
1489 } | |
1490 } | |
1491 // append type arguments | |
1492 if (hasArguments) { | |
1493 sb.append("<"); | |
1494 for (int i = 0; i < arguments.length; i++) { | |
1495 DartType argument = arguments[i]; | |
1496 if (i != 0) { | |
1497 sb.append(", "); | |
1498 } | |
1499 sb.append(getTypeSource2(argument)); | |
1500 } | |
1501 sb.append(">"); | |
1502 } | |
1503 } | |
1504 // done | |
1505 return sb.toString(); | |
1506 } | |
1507 | |
1508 /** | |
1509 * @return the source of the inverted condition for the given logical expressi
on. | |
1510 */ | |
1511 String invertCondition(Expression expression) => _invertCondition0(expression)
._source; | |
1512 | |
1513 /** | |
1514 * @return <code>true</code> if selection range contains only whitespace. | |
1515 */ | |
1516 bool isJustWhitespace(SourceRange range) => getText3(range).trim().length == 0
; | |
1517 | |
1518 /** | |
1519 * @return <code>true</code> if selection range contains only whitespace or co
mments | |
1520 */ | |
1521 bool isJustWhitespaceOrComment(SourceRange range) { | |
1522 String trimmedText = getText3(range).trim(); | |
1523 // may be whitespace | |
1524 if (trimmedText.isEmpty) { | |
1525 return true; | |
1526 } | |
1527 // may be comment | |
1528 return TokenUtils.getTokens(trimmedText).isEmpty; | |
1529 } | |
1530 | |
1531 /** | |
1532 * @return <code>true</code> if "selection" covers "node" and there are any no
n-whitespace tokens | |
1533 * between "selection" and "node" start/end. | |
1534 */ | |
1535 bool selectionIncludesNonWhitespaceOutsideNode(SourceRange selection, AstNode
node) => _selectionIncludesNonWhitespaceOutsideRange(selection, SourceRangeFacto
ry.rangeNode(node)); | |
1536 | |
1537 /** | |
1538 * @return <code>true</code> if given range of [BinaryExpression] can be extra
cted. | |
1539 */ | |
1540 bool validateBinaryExpressionRange(BinaryExpression binaryExpression, SourceRa
nge range) { | |
1541 // only parts of associative expression are safe to extract | |
1542 if (!binaryExpression.operator.type.isAssociativeOperator) { | |
1543 return false; | |
1544 } | |
1545 // prepare selected operands | |
1546 List<Expression> operands = _getOperandsInOrderFor(binaryExpression); | |
1547 List<Expression> subOperands = _getOperandsForSourceRange(operands, range); | |
1548 // if empty, then something wrong with selection | |
1549 if (subOperands.isEmpty) { | |
1550 return false; | |
1551 } | |
1552 // may be some punctuation included into selection - operators, braces, etc | |
1553 if (_selectionIncludesNonWhitespaceOutsideOperands(range, subOperands)) { | |
1554 return false; | |
1555 } | |
1556 // OK | |
1557 return true; | |
1558 } | |
1559 | |
1560 /** | |
1561 * @return the [ImportElement] used to import given [Element] into [library]. | |
1562 * May be `null` if was not imported, i.e. declared in the same librar
y. | |
1563 */ | |
1564 ImportElement _getImportElement(Element element) { | |
1565 for (ImportElement imp in _library.imports) { | |
1566 Map<String, Element> definedNames = getImportNamespace(imp); | |
1567 if (definedNames.containsValue(element)) { | |
1568 return imp; | |
1569 } | |
1570 } | |
1571 return null; | |
1572 } | |
1573 | |
1574 /** | |
1575 * @return the [InvertedCondition] for the given logical expression. | |
1576 */ | |
1577 CorrectionUtils_InvertedCondition _invertCondition0(Expression expression) { | |
1578 if (expression is BooleanLiteral) { | |
1579 BooleanLiteral literal = expression; | |
1580 if (literal.value) { | |
1581 return CorrectionUtils_InvertedCondition._simple("false"); | |
1582 } else { | |
1583 return CorrectionUtils_InvertedCondition._simple("true"); | |
1584 } | |
1585 } | |
1586 if (expression is BinaryExpression) { | |
1587 BinaryExpression binary = expression; | |
1588 TokenType operator = binary.operator.type; | |
1589 Expression le = binary.leftOperand; | |
1590 Expression re = binary.rightOperand; | |
1591 CorrectionUtils_InvertedCondition ls = _invertCondition0(le); | |
1592 CorrectionUtils_InvertedCondition rs = _invertCondition0(re); | |
1593 if (operator == TokenType.LT) { | |
1594 return CorrectionUtils_InvertedCondition._binary2(ls, " >= ", rs); | |
1595 } | |
1596 if (operator == TokenType.GT) { | |
1597 return CorrectionUtils_InvertedCondition._binary2(ls, " <= ", rs); | |
1598 } | |
1599 if (operator == TokenType.LT_EQ) { | |
1600 return CorrectionUtils_InvertedCondition._binary2(ls, " > ", rs); | |
1601 } | |
1602 if (operator == TokenType.GT_EQ) { | |
1603 return CorrectionUtils_InvertedCondition._binary2(ls, " < ", rs); | |
1604 } | |
1605 if (operator == TokenType.EQ_EQ) { | |
1606 return CorrectionUtils_InvertedCondition._binary2(ls, " != ", rs); | |
1607 } | |
1608 if (operator == TokenType.BANG_EQ) { | |
1609 return CorrectionUtils_InvertedCondition._binary2(ls, " == ", rs); | |
1610 } | |
1611 if (operator == TokenType.AMPERSAND_AMPERSAND) { | |
1612 int newPrecedence = TokenType.BAR_BAR.precedence; | |
1613 return CorrectionUtils_InvertedCondition._binary(newPrecedence, ls, " ||
", rs); | |
1614 } | |
1615 if (operator == TokenType.BAR_BAR) { | |
1616 int newPrecedence = TokenType.AMPERSAND_AMPERSAND.precedence; | |
1617 return CorrectionUtils_InvertedCondition._binary(newPrecedence, ls, " &&
", rs); | |
1618 } | |
1619 } | |
1620 if (expression is IsExpression) { | |
1621 IsExpression isExpression = expression; | |
1622 String expressionSource = getText(isExpression.expression); | |
1623 String typeSource = getText(isExpression.type); | |
1624 if (isExpression.notOperator == null) { | |
1625 return CorrectionUtils_InvertedCondition._simple("${expressionSource} is
! ${typeSource}"); | |
1626 } else { | |
1627 return CorrectionUtils_InvertedCondition._simple("${expressionSource} is
${typeSource}"); | |
1628 } | |
1629 } | |
1630 if (expression is PrefixExpression) { | |
1631 PrefixExpression prefixExpression = expression; | |
1632 TokenType operator = prefixExpression.operator.type; | |
1633 if (operator == TokenType.BANG) { | |
1634 Expression operand = prefixExpression.operand; | |
1635 while (operand is ParenthesizedExpression) { | |
1636 ParenthesizedExpression pe = operand as ParenthesizedExpression; | |
1637 operand = pe.expression; | |
1638 } | |
1639 return CorrectionUtils_InvertedCondition._simple(getText(operand)); | |
1640 } | |
1641 } | |
1642 if (expression is ParenthesizedExpression) { | |
1643 ParenthesizedExpression pe = expression; | |
1644 Expression innerExpresion = pe.expression; | |
1645 while (innerExpresion is ParenthesizedExpression) { | |
1646 innerExpresion = (innerExpresion as ParenthesizedExpression).expression; | |
1647 } | |
1648 return _invertCondition0(innerExpresion); | |
1649 } | |
1650 DartType type = expression.bestType; | |
1651 if (type.displayName == "bool") { | |
1652 return CorrectionUtils_InvertedCondition._simple("!${getText(expression)}"
); | |
1653 } | |
1654 return CorrectionUtils_InvertedCondition._simple(getText(expression)); | |
1655 } | |
1656 | |
1657 bool _selectionIncludesNonWhitespaceOutsideOperands(SourceRange selection, Lis
t<Expression> operands) => _selectionIncludesNonWhitespaceOutsideRange(selection
, SourceRangeFactory.rangeNodes(operands)); | |
1658 | |
1659 /** | |
1660 * @return <code>true</code> if "selection" covers "range" and there are any n
on-whitespace tokens | |
1661 * between "selection" and "range" start/end. | |
1662 */ | |
1663 bool _selectionIncludesNonWhitespaceOutsideRange(SourceRange selection, Source
Range range) { | |
1664 // selection should cover range | |
1665 if (!selection.covers(range)) { | |
1666 return false; | |
1667 } | |
1668 // non-whitespace between selection start and range start | |
1669 if (!isJustWhitespaceOrComment(SourceRangeFactory.rangeStartStart(selection,
range))) { | |
1670 return true; | |
1671 } | |
1672 // non-whitespace after range | |
1673 if (!isJustWhitespaceOrComment(SourceRangeFactory.rangeEndEnd(range, selecti
on))) { | |
1674 return true; | |
1675 } | |
1676 // only whitespace in selection around range | |
1677 return false; | |
1678 } | |
1679 } | |
1680 | |
1681 /** | |
1682 * Describes where to insert new directive or top-level declaration. | |
1683 */ | |
1684 class CorrectionUtils_InsertDesc { | |
1685 int offset = 0; | |
1686 | |
1687 String prefix = ""; | |
1688 | |
1689 String suffix = ""; | |
1690 } | |
1691 | |
1692 /** | |
1693 * This class is used to hold the source and also its precedence during invertin
g logical | |
1694 * expressions. | |
1695 */ | |
1696 class CorrectionUtils_InvertedCondition { | |
1697 static CorrectionUtils_InvertedCondition _binary(int precedence, CorrectionUti
ls_InvertedCondition left, String operation, CorrectionUtils_InvertedCondition r
ight) => new CorrectionUtils_InvertedCondition(precedence, "${CorrectionUtils._p
arenthesizeIfRequired(left, precedence)}${operation}${CorrectionUtils._parenthes
izeIfRequired(right, precedence)}"); | |
1698 | |
1699 static CorrectionUtils_InvertedCondition _binary2(CorrectionUtils_InvertedCond
ition left, String operation, CorrectionUtils_InvertedCondition right) => new Co
rrectionUtils_InvertedCondition(2147483647, "${left._source}${operation}${right.
_source}"); | |
1700 | |
1701 static CorrectionUtils_InvertedCondition _simple(String source) => new Correct
ionUtils_InvertedCondition(2147483647, source); | |
1702 | |
1703 final int _precedence; | |
1704 | |
1705 final String _source; | |
1706 | |
1707 CorrectionUtils_InvertedCondition(this._precedence, this._source); | |
1708 } | |
1709 | |
1710 class GeneralizingAstVisitor_CorrectionUtils_getOperandsInOrderFor extends Gener
alizingAstVisitor<Object> { | |
1711 TokenType groupOperatorType; | |
1712 | |
1713 List<Expression> operands; | |
1714 | |
1715 GeneralizingAstVisitor_CorrectionUtils_getOperandsInOrderFor(this.groupOperato
rType, this.operands) : super(); | |
1716 | |
1717 @override | |
1718 Object visitExpression(Expression node) { | |
1719 if (node is BinaryExpression && node.operator.type == groupOperatorType) { | |
1720 return super.visitNode(node); | |
1721 } | |
1722 operands.add(node); | |
1723 return null; | |
1724 } | |
1725 } | |
1726 | |
1727 class GeneralizingElementVisitor_CorrectionUtils_getChildren extends Generalizin
gElementVisitor<Object> { | |
1728 Element parent; | |
1729 | |
1730 String name; | |
1731 | |
1732 List<Element> children; | |
1733 | |
1734 GeneralizingElementVisitor_CorrectionUtils_getChildren(this.parent, this.name,
this.children) : super(); | |
1735 | |
1736 @override | |
1737 Object visitElement(Element element) { | |
1738 if (identical(element, parent)) { | |
1739 super.visitElement(element); | |
1740 } else if (name == null || CorrectionUtils.hasDisplayName(element, name)) { | |
1741 children.add(element); | |
1742 } | |
1743 return null; | |
1744 } | |
1745 } | |
1746 | |
1747 class GeneralizingElementVisitor_HierarchyUtils_getDirectMembers extends General
izingElementVisitor<Object> { | |
1748 ClassElement clazz; | |
1749 | |
1750 bool includeSynthetic = false; | |
1751 | |
1752 List<Element> members; | |
1753 | |
1754 GeneralizingElementVisitor_HierarchyUtils_getDirectMembers(this.clazz, this.in
cludeSynthetic, this.members) : super(); | |
1755 | |
1756 @override | |
1757 Object visitElement(Element element) { | |
1758 if (identical(element, clazz)) { | |
1759 return super.visitElement(element); | |
1760 } | |
1761 if (!includeSynthetic && element.isSynthetic) { | |
1762 return null; | |
1763 } | |
1764 if (element is ConstructorElement) { | |
1765 return null; | |
1766 } | |
1767 if (element is ExecutableElement) { | |
1768 members.add(element); | |
1769 } | |
1770 if (element is FieldElement) { | |
1771 members.add(element); | |
1772 } | |
1773 return null; | |
1774 } | |
1775 } | |
1776 | |
1777 class NameOccurrencesFinder extends RecursiveAstVisitor<Object> { | |
1778 static Iterable<AstNode> findIn(SimpleIdentifier ident, AstNode root) { | |
1779 if (ident == null || ident.bestElement == null) { | |
1780 return new Set<AstNode>(); | |
1781 } | |
1782 NameOccurrencesFinder finder = new NameOccurrencesFinder(ident.bestElement); | |
1783 root.accept(finder); | |
1784 return finder.matches; | |
1785 } | |
1786 | |
1787 Element _target; | |
1788 | |
1789 Element _target2; | |
1790 | |
1791 Element _target3; | |
1792 | |
1793 Element _target4; | |
1794 | |
1795 Set<AstNode> _matches; | |
1796 | |
1797 NameOccurrencesFinder(Element source) { | |
1798 this._target = source; | |
1799 while (true) { | |
1800 if (source.kind == ElementKind.GETTER || source.kind == ElementKind.SETTER
) { | |
1801 PropertyAccessorElement accessorElem = source as PropertyAccessorElement
; | |
1802 this._target2 = accessorElem.variable; | |
1803 if (source is Member) { | |
1804 Member member = source; | |
1805 this._target4 = member.baseElement; | |
1806 } | |
1807 if (this._target2 is Member) { | |
1808 Member member = source as Member; | |
1809 this._target3 = member.baseElement; | |
1810 } | |
1811 } else if (source.kind == ElementKind.FIELD || source.kind == ElementKind.
TOP_LEVEL_VARIABLE) { | |
1812 PropertyInducingElement propertyElem = source as PropertyInducingElement
; | |
1813 this._target2 = propertyElem.getter; | |
1814 this._target3 = propertyElem.setter; | |
1815 } else if (source.kind == ElementKind.METHOD) { | |
1816 if (source is Member) { | |
1817 Member member = source; | |
1818 this._target4 = member.baseElement; | |
1819 } | |
1820 } else if (source.kind == ElementKind.PARAMETER) { | |
1821 ParameterElement param = source as ParameterElement; | |
1822 if (param.isInitializingFormal) { | |
1823 FieldFormalParameterElement fieldInit = param as FieldFormalParameterE
lement; | |
1824 this._target2 = fieldInit.field; | |
1825 } | |
1826 } else { | |
1827 } | |
1828 break; | |
1829 } | |
1830 if (_target2 == null) { | |
1831 _target2 = _target; | |
1832 } | |
1833 if (_target3 == null) { | |
1834 _target3 = _target; | |
1835 } | |
1836 if (_target4 == null) { | |
1837 _target4 = _target; | |
1838 } | |
1839 this._matches = new Set<AstNode>(); | |
1840 } | |
1841 | |
1842 Iterable<AstNode> get matches => _matches; | |
1843 | |
1844 @override | |
1845 Object visitSimpleIdentifier(SimpleIdentifier node) { | |
1846 Element element = node.bestElement; | |
1847 if (element == null) { | |
1848 return null; | |
1849 } | |
1850 _match(element, node); | |
1851 if (element is Member) { | |
1852 Member member = element; | |
1853 _match(member.baseElement, node); | |
1854 } | |
1855 while (true) { | |
1856 if (element.kind == ElementKind.GETTER || element.kind == ElementKind.SETT
ER) { | |
1857 PropertyAccessorElement accessorElem = element as PropertyAccessorElemen
t; | |
1858 _match(accessorElem.variable, node); | |
1859 } else if (element.kind == ElementKind.FIELD || element.kind == ElementKin
d.TOP_LEVEL_VARIABLE) { | |
1860 PropertyInducingElement propertyElem = element as PropertyInducingElemen
t; | |
1861 _match(propertyElem.getter, node); | |
1862 _match(propertyElem.setter, node); | |
1863 } else if (element.kind == ElementKind.PARAMETER) { | |
1864 ParameterElement param = element as ParameterElement; | |
1865 if (param.isInitializingFormal) { | |
1866 FieldFormalParameterElement fieldInit = param as FieldFormalParameterE
lement; | |
1867 _match(fieldInit.field, node); | |
1868 } | |
1869 } else { | |
1870 } | |
1871 break; | |
1872 } | |
1873 return null; | |
1874 } | |
1875 | |
1876 void _match(Element element, AstNode node) { | |
1877 if (identical(_target, element) || identical(_target2, element) || identical
(_target3, element) || identical(_target4, element)) { | |
1878 _matches.add(node); | |
1879 } | |
1880 } | |
1881 } | |
1882 | |
1883 /** | |
1884 * Abstract visitor for visiting [AstNode]s covered by the selection [SourceRang
e]. | |
1885 */ | |
1886 class SelectionAnalyzer extends GeneralizingAstVisitor<Object> { | |
1887 SourceRange selection; | |
1888 | |
1889 AstNode _coveringNode; | |
1890 | |
1891 List<AstNode> _selectedNodes; | |
1892 | |
1893 SelectionAnalyzer(SourceRange selection) { | |
1894 assert(selection != null); | |
1895 this.selection = selection; | |
1896 } | |
1897 | |
1898 /** | |
1899 * @return the [AstNode] with the shortest length which completely covers the
specified | |
1900 * selection. | |
1901 */ | |
1902 AstNode get coveringNode => _coveringNode; | |
1903 | |
1904 /** | |
1905 * @return the first selected [AstNode], may be <code>null</code>. | |
1906 */ | |
1907 AstNode get firstSelectedNode { | |
1908 if (_selectedNodes == null || _selectedNodes.isEmpty) { | |
1909 return null; | |
1910 } | |
1911 return _selectedNodes[0]; | |
1912 } | |
1913 | |
1914 /** | |
1915 * @return the last selected [AstNode], may be <code>null</code>. | |
1916 */ | |
1917 AstNode get lastSelectedNode { | |
1918 if (_selectedNodes == null || _selectedNodes.isEmpty) { | |
1919 return null; | |
1920 } | |
1921 return _selectedNodes[_selectedNodes.length - 1]; | |
1922 } | |
1923 | |
1924 /** | |
1925 * @return the [SourceRange] which covers selected [AstNode]s, may be | |
1926 * <code>null</code> if no [AstNode]s under selection. | |
1927 */ | |
1928 SourceRange get selectedNodeRange { | |
1929 if (_selectedNodes == null || _selectedNodes.isEmpty) { | |
1930 return null; | |
1931 } | |
1932 AstNode firstNode = _selectedNodes[0]; | |
1933 AstNode lastNode = _selectedNodes[_selectedNodes.length - 1]; | |
1934 return SourceRangeFactory.rangeStartEnd(firstNode, lastNode); | |
1935 } | |
1936 | |
1937 /** | |
1938 * @return the [AstNode]s fully covered by the selection [SourceRange]. | |
1939 */ | |
1940 List<AstNode> get selectedNodes { | |
1941 if (_selectedNodes == null || _selectedNodes.isEmpty) { | |
1942 return []; | |
1943 } | |
1944 return _selectedNodes; | |
1945 } | |
1946 | |
1947 /** | |
1948 * @return <code>true</code> if there are [AstNode] fully covered by the selec
tion | |
1949 * [SourceRange]. | |
1950 */ | |
1951 bool get hasSelectedNodes => _selectedNodes != null && !_selectedNodes.isEmpty
; | |
1952 | |
1953 @override | |
1954 Object visitNode(AstNode node) { | |
1955 SourceRange nodeRange = SourceRangeFactory.rangeNode(node); | |
1956 if (selection.covers(nodeRange)) { | |
1957 if (isFirstNode) { | |
1958 handleFirstSelectedNode(node); | |
1959 } else { | |
1960 handleNextSelectedNode(node); | |
1961 } | |
1962 return null; | |
1963 } else if (selection.coveredBy(nodeRange)) { | |
1964 _coveringNode = node; | |
1965 node.visitChildren(this); | |
1966 return null; | |
1967 } else if (selection.startsIn(nodeRange)) { | |
1968 handleSelectionStartsIn(node); | |
1969 node.visitChildren(this); | |
1970 return null; | |
1971 } else if (selection.endsIn(nodeRange)) { | |
1972 handleSelectionEndsIn(node); | |
1973 node.visitChildren(this); | |
1974 return null; | |
1975 } | |
1976 // no intersection | |
1977 return null; | |
1978 } | |
1979 | |
1980 /** | |
1981 * Adds first selected [AstNode]. | |
1982 */ | |
1983 void handleFirstSelectedNode(AstNode node) { | |
1984 _selectedNodes = []; | |
1985 _selectedNodes.add(node); | |
1986 } | |
1987 | |
1988 /** | |
1989 * Adds second or more selected [AstNode]. | |
1990 */ | |
1991 void handleNextSelectedNode(AstNode node) { | |
1992 if (identical(firstSelectedNode.parent, node.parent)) { | |
1993 _selectedNodes.add(node); | |
1994 } | |
1995 } | |
1996 | |
1997 /** | |
1998 * Notifies that selection ends in given [AstNode]. | |
1999 */ | |
2000 void handleSelectionEndsIn(AstNode node) { | |
2001 } | |
2002 | |
2003 /** | |
2004 * Notifies that selection starts in given [AstNode]. | |
2005 */ | |
2006 void handleSelectionStartsIn(AstNode node) { | |
2007 } | |
2008 | |
2009 /** | |
2010 * Resets selected nodes. | |
2011 */ | |
2012 void reset() { | |
2013 _selectedNodes = null; | |
2014 } | |
2015 | |
2016 /** | |
2017 * @return <code>true</code> if there was no selected nodes yet. | |
2018 */ | |
2019 bool get isFirstNode => _selectedNodes == null; | |
2020 } | |
2021 | |
2022 /** | |
2023 * Helper for building Dart source with tracked positions. | |
2024 */ | |
2025 class SourceBuilder { | |
2026 final int offset; | |
2027 | |
2028 JavaStringBuilder _buffer = new JavaStringBuilder(); | |
2029 | |
2030 Map<String, List<SourceRange>> _linkedPositions = {}; | |
2031 | |
2032 final Map<String, List<LinkedPositionProposal>> linkedProposals = {}; | |
2033 | |
2034 String _currentPositionGroupId; | |
2035 | |
2036 int _currentPositionStart = 0; | |
2037 | |
2038 int _endPosition = -1; | |
2039 | |
2040 SourceBuilder.con1(this.offset); | |
2041 | |
2042 SourceBuilder.con2(SourceRange offsetRange) : this.con1(offsetRange.offset); | |
2043 | |
2044 /** | |
2045 * Adds proposal for the current position, may be called after [startPosition]
. | |
2046 */ | |
2047 void addProposal(CorrectionImage icon, String text) { | |
2048 List<LinkedPositionProposal> proposals = linkedProposals[_currentPositionGro
upId]; | |
2049 if (proposals == null) { | |
2050 proposals = []; | |
2051 linkedProposals[_currentPositionGroupId] = proposals; | |
2052 } | |
2053 proposals.add(new LinkedPositionProposal(icon, text)); | |
2054 } | |
2055 | |
2056 /** | |
2057 * Appends source to the buffer. | |
2058 */ | |
2059 SourceBuilder append(String s) { | |
2060 _buffer.append(s); | |
2061 return this; | |
2062 } | |
2063 | |
2064 /** | |
2065 * Ends position started using [startPosition]. | |
2066 */ | |
2067 void endPosition() { | |
2068 assert(_currentPositionGroupId != null); | |
2069 _addPosition(); | |
2070 _currentPositionGroupId = null; | |
2071 } | |
2072 | |
2073 /** | |
2074 * @return the "end position" for the [CorrectionProposal], may be <code>-1</c
ode> if not | |
2075 * set in this [SourceBuilder]. | |
2076 */ | |
2077 int get endPosition2 { | |
2078 if (_endPosition == -1) { | |
2079 return -1; | |
2080 } | |
2081 return offset + _endPosition; | |
2082 } | |
2083 | |
2084 /** | |
2085 * @return the [Map] or position IDs to their locations. | |
2086 */ | |
2087 Map<String, List<SourceRange>> get linkedPositions => _linkedPositions; | |
2088 | |
2089 /** | |
2090 * @return the length of the built source. | |
2091 */ | |
2092 int length() => _buffer.length; | |
2093 | |
2094 /** | |
2095 * Marks current position as "end position" of the [CorrectionProposal]. | |
2096 */ | |
2097 void setEndPosition() { | |
2098 _endPosition = _buffer.length; | |
2099 } | |
2100 | |
2101 /** | |
2102 * Sets text-only proposals for the current position. | |
2103 */ | |
2104 void set proposals(List<String> proposals) { | |
2105 List<LinkedPositionProposal> proposalList = []; | |
2106 for (String proposalText in proposals) { | |
2107 proposalList.add(new LinkedPositionProposal(null, proposalText)); | |
2108 } | |
2109 linkedProposals[_currentPositionGroupId] = proposalList; | |
2110 } | |
2111 | |
2112 /** | |
2113 * Starts linked position with given ID. | |
2114 */ | |
2115 void startPosition(String groupId) { | |
2116 assert(_currentPositionGroupId == null); | |
2117 _currentPositionGroupId = groupId; | |
2118 _currentPositionStart = _buffer.length; | |
2119 } | |
2120 | |
2121 @override | |
2122 String toString() => _buffer.toString(); | |
2123 | |
2124 /** | |
2125 * Adds position location [SourceRange] using current fields. | |
2126 */ | |
2127 void _addPosition() { | |
2128 List<SourceRange> locations = _linkedPositions[_currentPositionGroupId]; | |
2129 if (locations == null) { | |
2130 locations = []; | |
2131 _linkedPositions[_currentPositionGroupId] = locations; | |
2132 } | |
2133 int start = offset + _currentPositionStart; | |
2134 int end = offset + _buffer.length; | |
2135 locations.add(SourceRangeFactory.rangeStartEnd(start, end)); | |
2136 } | |
2137 } | |
2138 | |
2139 /** | |
2140 * Analyzer to check if a selection covers a valid set of statements of AST. | |
2141 */ | |
2142 class StatementAnalyzer extends SelectionAnalyzer { | |
2143 /** | |
2144 * @return <code>true</code> if "nodes" contains "node". | |
2145 */ | |
2146 static bool _contains(List<AstNode> nodes, AstNode node) => nodes.contains(nod
e); | |
2147 | |
2148 /** | |
2149 * @return <code>true</code> if "nodes" contains one of the "otherNodes". | |
2150 */ | |
2151 static bool _contains2(List<AstNode> nodes, List<AstNode> otherNodes) { | |
2152 for (AstNode otherNode in otherNodes) { | |
2153 if (nodes.contains(otherNode)) { | |
2154 return true; | |
2155 } | |
2156 } | |
2157 return false; | |
2158 } | |
2159 | |
2160 CorrectionUtils utils; | |
2161 | |
2162 RefactoringStatus _status = new RefactoringStatus(); | |
2163 | |
2164 StatementAnalyzer.con1(CompilationUnit cunit, SourceRange selection) : this.co
n2(new CorrectionUtils(cunit), selection); | |
2165 | |
2166 StatementAnalyzer.con2(CorrectionUtils utils, SourceRange selection) : super(s
election) { | |
2167 this.utils = utils; | |
2168 } | |
2169 | |
2170 /** | |
2171 * @return the [RefactoringStatus] result of checking selection. | |
2172 */ | |
2173 RefactoringStatus get status => _status; | |
2174 | |
2175 @override | |
2176 Object visitCompilationUnit(CompilationUnit node) { | |
2177 super.visitCompilationUnit(node); | |
2178 if (!hasSelectedNodes) { | |
2179 return null; | |
2180 } | |
2181 // check that selection does not begin/end in comment | |
2182 { | |
2183 int selectionStart = selection.offset; | |
2184 int selectionEnd = selection.end; | |
2185 List<SourceRange> commentRanges = utils.commentRanges; | |
2186 for (SourceRange commentRange in commentRanges) { | |
2187 if (commentRange.contains(selectionStart)) { | |
2188 invalidSelection("Selection begins inside a comment."); | |
2189 } | |
2190 if (commentRange.containsExclusive(selectionEnd)) { | |
2191 invalidSelection("Selection ends inside a comment."); | |
2192 } | |
2193 } | |
2194 } | |
2195 // more checks | |
2196 if (!_status.hasFatalError) { | |
2197 _checkSelectedNodes(node); | |
2198 } | |
2199 return null; | |
2200 } | |
2201 | |
2202 @override | |
2203 Object visitDoStatement(DoStatement node) { | |
2204 super.visitDoStatement(node); | |
2205 List<AstNode> selectedNodes = this.selectedNodes; | |
2206 if (_contains(selectedNodes, node.body)) { | |
2207 invalidSelection("Operation not applicable to a 'do' statement's body and
expression."); | |
2208 } | |
2209 return null; | |
2210 } | |
2211 | |
2212 @override | |
2213 Object visitForStatement(ForStatement node) { | |
2214 super.visitForStatement(node); | |
2215 List<AstNode> selectedNodes = this.selectedNodes; | |
2216 bool containsInit = _contains(selectedNodes, node.initialization) || _contai
ns(selectedNodes, node.variables); | |
2217 bool containsCondition = _contains(selectedNodes, node.condition); | |
2218 bool containsUpdaters = _contains2(selectedNodes, node.updaters); | |
2219 bool containsBody = _contains(selectedNodes, node.body); | |
2220 if (containsInit && containsCondition) { | |
2221 invalidSelection("Operation not applicable to a 'for' statement's initiali
zer and condition."); | |
2222 } else if (containsCondition && containsUpdaters) { | |
2223 invalidSelection("Operation not applicable to a 'for' statement's conditio
n and updaters."); | |
2224 } else if (containsUpdaters && containsBody) { | |
2225 invalidSelection("Operation not applicable to a 'for' statement's updaters
and body."); | |
2226 } | |
2227 return null; | |
2228 } | |
2229 | |
2230 @override | |
2231 Object visitSwitchStatement(SwitchStatement node) { | |
2232 super.visitSwitchStatement(node); | |
2233 List<AstNode> selectedNodes = this.selectedNodes; | |
2234 List<SwitchMember> switchMembers = node.members; | |
2235 for (AstNode selectedNode in selectedNodes) { | |
2236 if (switchMembers.contains(selectedNode)) { | |
2237 invalidSelection("Selection must either cover whole switch statement or
parts of a single case block."); | |
2238 break; | |
2239 } | |
2240 } | |
2241 return null; | |
2242 } | |
2243 | |
2244 @override | |
2245 Object visitTryStatement(TryStatement node) { | |
2246 super.visitTryStatement(node); | |
2247 AstNode firstSelectedNode = this.firstSelectedNode; | |
2248 if (firstSelectedNode != null) { | |
2249 if (identical(firstSelectedNode, node.body) || identical(firstSelectedNode
, node.finallyBlock)) { | |
2250 invalidSelection("Selection must either cover whole try statement or par
ts of try, catch, or finally block."); | |
2251 } else { | |
2252 List<CatchClause> catchClauses = node.catchClauses; | |
2253 for (CatchClause catchClause in catchClauses) { | |
2254 if (identical(firstSelectedNode, catchClause) || identical(firstSelect
edNode, catchClause.body) || identical(firstSelectedNode, catchClause.exceptionP
arameter)) { | |
2255 invalidSelection("Selection must either cover whole try statement or
parts of try, catch, or finally block."); | |
2256 } | |
2257 } | |
2258 } | |
2259 } | |
2260 return null; | |
2261 } | |
2262 | |
2263 @override | |
2264 Object visitWhileStatement(WhileStatement node) { | |
2265 super.visitWhileStatement(node); | |
2266 List<AstNode> selectedNodes = this.selectedNodes; | |
2267 if (_contains(selectedNodes, node.condition) && _contains(selectedNodes, nod
e.body)) { | |
2268 invalidSelection("Operation not applicable to a while statement's expressi
on and body."); | |
2269 } | |
2270 return null; | |
2271 } | |
2272 | |
2273 /** | |
2274 * Records fatal error with given message. | |
2275 */ | |
2276 void invalidSelection(String message) { | |
2277 invalidSelection2(message, null); | |
2278 } | |
2279 | |
2280 /** | |
2281 * Records fatal error with given message and [RefactoringStatusContext]. | |
2282 */ | |
2283 void invalidSelection2(String message, RefactoringStatusContext context) { | |
2284 _status.addFatalError(message, context); | |
2285 reset(); | |
2286 } | |
2287 | |
2288 /** | |
2289 * Checks final selected [AstNode]s after processing [CompilationUnit]. | |
2290 */ | |
2291 void _checkSelectedNodes(CompilationUnit unit) { | |
2292 List<AstNode> nodes = selectedNodes; | |
2293 // some tokens before first selected node | |
2294 { | |
2295 AstNode firstNode = nodes[0]; | |
2296 SourceRange rangeBeforeFirstNode = SourceRangeFactory.rangeStartStart(sele
ction, firstNode); | |
2297 if (_hasTokens(rangeBeforeFirstNode)) { | |
2298 invalidSelection2("The beginning of the selection contains characters th
at do not belong to a statement.", new RefactoringStatusContext.forUnit(unit, ra
ngeBeforeFirstNode)); | |
2299 } | |
2300 } | |
2301 // some tokens after last selected node | |
2302 { | |
2303 AstNode lastNode = nodes.last; | |
2304 SourceRange rangeAfterLastNode = SourceRangeFactory.rangeEndEnd(lastNode,
selection); | |
2305 if (_hasTokens(rangeAfterLastNode)) { | |
2306 invalidSelection2("The end of the selection contains characters that do
not belong to a statement.", new RefactoringStatusContext.forUnit(unit, rangeAft
erLastNode)); | |
2307 } | |
2308 } | |
2309 } | |
2310 | |
2311 /** | |
2312 * @return the [Token]s in given [SourceRange]. | |
2313 */ | |
2314 List<Token> _getTokens(SourceRange range) { | |
2315 try { | |
2316 String text = utils.getText3(range); | |
2317 return TokenUtils.getTokens(text); | |
2318 } catch (e) { | |
2319 return []; | |
2320 } | |
2321 } | |
2322 | |
2323 /** | |
2324 * @return <code>true</code> if there are [Token]s in the given [SourceRange]. | |
2325 */ | |
2326 bool _hasTokens(SourceRange range) => !_getTokens(range).isEmpty; | |
2327 } | |
2328 | |
2329 /** | |
2330 * Utilities to work with [Token]s. | |
2331 */ | |
2332 class TokenUtils { | |
2333 /** | |
2334 * @return the first [KeywordToken] with given [Keyword], may be <code>null</c
ode> if | |
2335 * not found. | |
2336 */ | |
2337 static KeywordToken findKeywordToken(List<Token> tokens, Keyword keyword) { | |
2338 for (Token token in tokens) { | |
2339 if (token is KeywordToken) { | |
2340 KeywordToken keywordToken = token; | |
2341 if (keywordToken.keyword == keyword) { | |
2342 return keywordToken; | |
2343 } | |
2344 } | |
2345 } | |
2346 return null; | |
2347 } | |
2348 | |
2349 /** | |
2350 * @return the first [Token] with given [TokenType], may be <code>null</code>
if not | |
2351 * found. | |
2352 */ | |
2353 static Token findToken(List<Token> tokens, TokenType type) { | |
2354 for (Token token in tokens) { | |
2355 if (token.type == type) { | |
2356 return token; | |
2357 } | |
2358 } | |
2359 return null; | |
2360 } | |
2361 | |
2362 /** | |
2363 * @return [Token]s of the given Dart source, not <code>null</code>, may be em
pty if no | |
2364 * tokens or some exception happens. | |
2365 */ | |
2366 static List<Token> getTokens(String s) { | |
2367 try { | |
2368 List<Token> tokens = []; | |
2369 Scanner scanner = new Scanner(null, new CharSequenceReader(s), null); | |
2370 Token token = scanner.tokenize(); | |
2371 while (token.type != TokenType.EOF) { | |
2372 tokens.add(token); | |
2373 token = token.next; | |
2374 } | |
2375 return tokens; | |
2376 } catch (e) { | |
2377 return []; | |
2378 } | |
2379 } | |
2380 | |
2381 /** | |
2382 * @return <code>true</code> if given [Token]s contain only single [Token] wit
h given | |
2383 * [TokenType]. | |
2384 */ | |
2385 static bool hasOnly(List<Token> tokens, TokenType type) => tokens.length == 1
&& tokens[0].type == type; | |
2386 } | |
2387 | |
2388 class URIUtils { | |
2389 /** | |
2390 * Computes relative relative path to reference "target" from "base". Uses "..
" if needed, in | |
2391 * contrast to [URI#relativize]. | |
2392 */ | |
2393 static String computeRelativePath(String base, String target) { | |
2394 // convert to URI separator | |
2395 base = base.replaceAll("\\\\", "/"); | |
2396 target = target.replaceAll("\\\\", "/"); | |
2397 if (base.startsWith("/") && target.startsWith("/")) { | |
2398 base = base.substring(1); | |
2399 target = target.substring(1); | |
2400 } | |
2401 // equal paths - no relative | |
2402 if (base == target) { | |
2403 return null; | |
2404 } | |
2405 // split paths | |
2406 List<String> baseParts = base.split("/"); | |
2407 List<String> targetParts = target.split("/"); | |
2408 // prepare maximum possible common root length | |
2409 int length = baseParts.length < targetParts.length ? baseParts.length : targ
etParts.length; | |
2410 // find common root | |
2411 int lastCommonRoot = -1; | |
2412 for (int i = 0; i < length; i++) { | |
2413 if (baseParts[i] == targetParts[i]) { | |
2414 lastCommonRoot = i; | |
2415 } else { | |
2416 break; | |
2417 } | |
2418 } | |
2419 // append .. | |
2420 JavaStringBuilder relativePath = new JavaStringBuilder(); | |
2421 for (int i = lastCommonRoot + 1; i < baseParts.length; i++) { | |
2422 if (baseParts[i].length > 0) { | |
2423 relativePath.append("../"); | |
2424 } | |
2425 } | |
2426 // append target folder names | |
2427 for (int i = lastCommonRoot + 1; i < targetParts.length - 1; i++) { | |
2428 String p = targetParts[i]; | |
2429 relativePath.append(p); | |
2430 relativePath.append("/"); | |
2431 } | |
2432 // append target file name | |
2433 relativePath.append(targetParts[targetParts.length - 1]); | |
2434 // done | |
2435 return relativePath.toString(); | |
2436 } | |
2437 } | |
OLD | NEW |