Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(169)

Side by Side Diff: pkg/analysis_services/lib/src/generated/util.dart

Issue 484733003: Import analysis_services.dart into analysis_server.dart. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/analysis_services/lib/src/generated/stubs.dart ('k') | pkg/analysis_services/lib/src/index/index_contributor.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698