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

Side by Side Diff: pkg/analysis_server/lib/src/services/correction/util.dart

Issue 489973002: Initial 'Extract Local' implementation. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: tweaks 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
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library services.src.correction.util; 5 library services.src.correction.util;
6 6
7 import 'dart:math';
8
7 import 'package:analysis_server/src/protocol2.dart' show SourceEdit; 9 import 'package:analysis_server/src/protocol2.dart' show SourceEdit;
8 import 'package:analysis_server/src/services/correction/source_range.dart'; 10 import 'package:analysis_server/src/services/correction/source_range.dart';
9 import 'package:analysis_server/src/services/correction/strings.dart'; 11 import 'package:analysis_server/src/services/correction/strings.dart';
10 import 'package:analyzer/src/generated/ast.dart'; 12 import 'package:analyzer/src/generated/ast.dart';
11 import 'package:analyzer/src/generated/element.dart'; 13 import 'package:analyzer/src/generated/element.dart';
12 import 'package:analyzer/src/generated/engine.dart'; 14 import 'package:analyzer/src/generated/engine.dart';
13 import 'package:analyzer/src/generated/resolver.dart'; 15 import 'package:analyzer/src/generated/resolver.dart';
14 import 'package:analyzer/src/generated/scanner.dart'; 16 import 'package:analyzer/src/generated/scanner.dart';
15 import 'package:analyzer/src/generated/source.dart'; 17 import 'package:analyzer/src/generated/source.dart';
16 18
17 19
18 /** 20 /**
21 * @return <code>true</code> if given [List]s are identical at given position.
22 */
23 bool allListsIdentical(List<List> lists, int position) {
24 Object element = lists[0][position];
25 for (List list in lists) {
26 if (list[position] != element) {
27 return false;
28 }
29 }
30 return true;
31 }
32
33
34 /**
19 * TODO(scheglov) replace with nodes once there will be [CompilationUnit#getComm ents]. 35 * TODO(scheglov) replace with nodes once there will be [CompilationUnit#getComm ents].
20 * 36 *
21 * Returns [SourceRange]s of all comments in [unit]. 37 * Returns [SourceRange]s of all comments in [unit].
22 */ 38 */
23 List<SourceRange> getCommentRanges(CompilationUnit unit) { 39 List<SourceRange> getCommentRanges(CompilationUnit unit) {
24 List<SourceRange> ranges = <SourceRange>[]; 40 List<SourceRange> ranges = <SourceRange>[];
25 Token token = unit.beginToken; 41 Token token = unit.beginToken;
26 while (token != null && token.type != TokenType.EOF) { 42 while (token != null && token.type != TokenType.EOF) {
27 Token commentToken = token.precedingComments; 43 Token commentToken = token.precedingComments;
28 while (commentToken != null) { 44 while (commentToken != null) {
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
89 return node.element; 105 return node.element;
90 } 106 }
91 if (node is MethodDeclaration) { 107 if (node is MethodDeclaration) {
92 return node.element; 108 return node.element;
93 } 109 }
94 node = node.parent; 110 node = node.parent;
95 } 111 }
96 return null; 112 return null;
97 } 113 }
98 114
115
116 /**
117 * @return the enclosing executable [AstNode].
118 */
119 AstNode getEnclosingExecutableNode(AstNode node) {
120 while (node != null) {
121 if (node is FunctionDeclaration) {
122 return node;
123 }
124 if (node is ConstructorDeclaration) {
125 return node;
126 }
127 if (node is MethodDeclaration) {
128 return node;
129 }
130 node = node.parent;
131 }
132 return null;
133 }
134
135
99 /** 136 /**
100 * Returns [getExpressionPrecedence] for the parent of [node], 137 * Returns [getExpressionPrecedence] for the parent of [node],
101 * or `0` if the parent node is [ParenthesizedExpression]. 138 * or `0` if the parent node is [ParenthesizedExpression].
102 * 139 *
103 * The reason is that `(expr)` is always executed after `expr`. 140 * The reason is that `(expr)` is always executed after `expr`.
104 */ 141 */
105 int getExpressionParentPrecedence(AstNode node) { 142 int getExpressionParentPrecedence(AstNode node) {
106 AstNode parent = node.parent; 143 AstNode parent = node.parent;
107 if (parent is ParenthesizedExpression) { 144 if (parent is ParenthesizedExpression) {
108 return 0; 145 return 0;
109 } 146 }
110 return getExpressionPrecedence(parent); 147 return getExpressionPrecedence(parent);
111 } 148 }
112 149
150
113 /** 151 /**
114 * Returns the precedence of [node] it is an [Expression], negative otherwise. 152 * Returns the precedence of [node] it is an [Expression], negative otherwise.
115 */ 153 */
116 int getExpressionPrecedence(AstNode node) { 154 int getExpressionPrecedence(AstNode node) {
117 if (node is Expression) { 155 if (node is Expression) {
118 return node.precedence; 156 return node.precedence;
119 } 157 }
120 return -1000; 158 return -1000;
121 } 159 }
122 160
161
123 /** 162 /**
124 * Returns the namespace of the given [ImportElement]. 163 * Returns the namespace of the given [ImportElement].
125 */ 164 */
126 Map<String, Element> getImportNamespace(ImportElement imp) { 165 Map<String, Element> getImportNamespace(ImportElement imp) {
127 NamespaceBuilder builder = new NamespaceBuilder(); 166 NamespaceBuilder builder = new NamespaceBuilder();
128 Namespace namespace = builder.createImportNamespaceForDirective(imp); 167 Namespace namespace = builder.createImportNamespaceForDirective(imp);
129 return namespace.definedNames; 168 return namespace.definedNames;
130 } 169 }
131 170
171 /**
172 * @return the nearest common ancestor [AstNode] of the given [AstNode]s.
173 */
174 AstNode getNearestCommonAncestor(List<AstNode> nodes) {
175 // may be no nodes
176 if (nodes.isEmpty) {
177 return null;
178 }
179 // prepare parents
180 List<List<AstNode>> parents = [];
181 for (AstNode node in nodes) {
182 parents.add(getParents(node));
183 }
184 // find min length
185 int minLength = 1 << 20;
186 for (List<AstNode> parentList in parents) {
187 minLength = min(minLength, parentList.length);
188 }
189 // find deepest parent
190 int i = 0;
191 for (; i < minLength; i++) {
192 if (!allListsIdentical(parents, i)) {
193 break;
194 }
195 }
196 return parents[0][i - 1];
197 }
198
199 /**
200 * @return parent [AstNode]s from [CompilationUnit] (at index "0") to the given one.
201 */
202 List<AstNode> getParents(AstNode node) {
203 // prepare number of parents
204 int numParents = 0;
205 {
206 AstNode current = node.parent;
207 while (current != null) {
208 numParents++;
209 current = current.parent;
210 }
211 }
212 // fill array of parents
213 List<AstNode> parents = new List<AstNode>(numParents);
214 AstNode current = node.parent;
215 int index = numParents;
216 while (current != null) {
217 parents[--index] = current;
218 current = current.parent;
219 }
220 return parents;
221 }
222
132 223
133 /** 224 /**
134 * If given [AstNode] is name of qualified property extraction, returns target f rom which 225 * If given [AstNode] is name of qualified property extraction, returns target f rom which
135 * this property is extracted. Otherwise `null`. 226 * this property is extracted. Otherwise `null`.
136 */ 227 */
137 Expression getQualifiedPropertyTarget(AstNode node) { 228 Expression getQualifiedPropertyTarget(AstNode node) {
138 AstNode parent = node.parent; 229 AstNode parent = node.parent;
139 if (parent is PrefixedIdentifier) { 230 if (parent is PrefixedIdentifier) {
140 PrefixedIdentifier prefixed = parent; 231 PrefixedIdentifier prefixed = parent;
141 if (prefixed.identifier == node) { 232 if (prefixed.identifier == node) {
(...skipping 525 matching lines...) Expand 10 before | Expand all | Expand 10 after
667 return sb.toString(); 758 return sb.toString();
668 } 759 }
669 760
670 /** 761 /**
671 * @return the source of the inverted condition for the given logical expressi on. 762 * @return the source of the inverted condition for the given logical expressi on.
672 */ 763 */
673 String invertCondition(Expression expression) => 764 String invertCondition(Expression expression) =>
674 _invertCondition0(expression)._source; 765 _invertCondition0(expression)._source;
675 766
676 /** 767 /**
768 * @return <code>true</code> if selection range contains only whitespace or co mments
769 */
770 bool isJustWhitespaceOrComment(SourceRange range) {
771 String trimmedText = getRangeText(range).trim();
772 // may be whitespace
773 if (trimmedText.isEmpty) {
774 return true;
775 }
776 // may be comment
777 return TokenUtils.getTokens(trimmedText).isEmpty;
778 }
779
780 /**
677 * Returns the source with indentation changed from [oldIndent] to 781 * Returns the source with indentation changed from [oldIndent] to
678 * [newIndent], keeping indentation of lines relative to each other. 782 * [newIndent], keeping indentation of lines relative to each other.
679 */ 783 */
680 String replaceSourceIndent(String source, String oldIndent, 784 String replaceSourceIndent(String source, String oldIndent,
681 String newIndent) { 785 String newIndent) {
682 // prepare STRING token ranges 786 // prepare STRING token ranges
683 List<SourceRange> lineRanges = []; 787 List<SourceRange> lineRanges = [];
684 { 788 {
685 var token = unit.beginToken; 789 var token = unit.beginToken;
686 while (token != null && token.type != TokenType.EOF) { 790 while (token != null && token.type != TokenType.EOF) {
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
728 * from [oldIndent] to [newIndent], keeping indentation of lines relative 832 * from [oldIndent] to [newIndent], keeping indentation of lines relative
729 * to each other. 833 * to each other.
730 */ 834 */
731 String replaceSourceRangeIndent(SourceRange range, String oldIndent, 835 String replaceSourceRangeIndent(SourceRange range, String oldIndent,
732 String newIndent) { 836 String newIndent) {
733 String oldSource = getRangeText(range); 837 String oldSource = getRangeText(range);
734 return replaceSourceIndent(oldSource, oldIndent, newIndent); 838 return replaceSourceIndent(oldSource, oldIndent, newIndent);
735 } 839 }
736 840
737 /** 841 /**
842 * @return <code>true</code> if "selection" covers "node" and there are any no n-whitespace tokens
843 * between "selection" and "node" start/end.
844 */
845 bool selectionIncludesNonWhitespaceOutsideNode(SourceRange selection,
846 AstNode node) {
847 return _selectionIncludesNonWhitespaceOutsideRange(
848 selection,
849 rangeNode(node));
850 }
851
852 /**
853 * @return <code>true</code> if given range of [BinaryExpression] can be extra cted.
854 */
855 bool validateBinaryExpressionRange(BinaryExpression binaryExpression, SourceRa nge range) {
856 // only parts of associative expression are safe to extract
857 if (!binaryExpression.operator.type.isAssociativeOperator) {
858 return false;
859 }
860 // prepare selected operands
861 List<Expression> operands = _getOperandsInOrderFor(binaryExpression);
862 List<Expression> subOperands = _getOperandsForSourceRange(operands, range);
863 // if empty, then something wrong with selection
864 if (subOperands.isEmpty) {
865 return false;
866 }
867 // may be some punctuation included into selection - operators, braces, etc
868 if (_selectionIncludesNonWhitespaceOutsideOperands(range, subOperands)) {
869 return false;
870 }
871 // OK
872 return true;
873 }
874
875 /**
738 * @return the [ImportElement] used to import given [Element] into [library]. 876 * @return the [ImportElement] used to import given [Element] into [library].
739 * May be `null` if was not imported, i.e. declared in the same librar y. 877 * May be `null` if was not imported, i.e. declared in the same librar y.
740 */ 878 */
741 ImportElement _getImportElement(Element element) { 879 ImportElement _getImportElement(Element element) {
742 for (ImportElement imp in _library.imports) { 880 for (ImportElement imp in _library.imports) {
743 Map<String, Element> definedNames = getImportNamespace(imp); 881 Map<String, Element> definedNames = getImportNamespace(imp);
744 if (definedNames.containsValue(element)) { 882 if (definedNames.containsValue(element)) {
745 return imp; 883 return imp;
746 } 884 }
747 } 885 }
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
831 innerExpresion = (innerExpresion as ParenthesizedExpression).expression; 969 innerExpresion = (innerExpresion as ParenthesizedExpression).expression;
832 } 970 }
833 return _invertCondition0(innerExpresion); 971 return _invertCondition0(innerExpresion);
834 } 972 }
835 DartType type = expression.bestType; 973 DartType type = expression.bestType;
836 if (type.displayName == "bool") { 974 if (type.displayName == "bool") {
837 return _InvertedCondition._simple("!${getNodeText(expression)}"); 975 return _InvertedCondition._simple("!${getNodeText(expression)}");
838 } 976 }
839 return _InvertedCondition._simple(getNodeText(expression)); 977 return _InvertedCondition._simple(getNodeText(expression));
840 } 978 }
979
980 bool _selectionIncludesNonWhitespaceOutsideOperands(SourceRange selection, Lis t<Expression> operands) {
981 return _selectionIncludesNonWhitespaceOutsideRange(selection, rangeNodes(ope rands));
982 }
983
984 /**
985 * @return <code>true</code> if "selection" covers "range" and there are any n on-whitespace tokens
986 * between "selection" and "range" start/end.
987 */
988 bool _selectionIncludesNonWhitespaceOutsideRange(SourceRange selection,
989 SourceRange range) {
990 // selection should cover range
991 if (!selection.covers(range)) {
992 return false;
993 }
994 // non-whitespace between selection start and range start
995 if (!isJustWhitespaceOrComment(rangeStartStart(selection, range))) {
996 return true;
997 }
998 // non-whitespace after range
999 if (!isJustWhitespaceOrComment(rangeEndEnd(range, selection))) {
1000 return true;
1001 }
1002 // only whitespace in selection around range
1003 return false;
1004 }
1005
1006 /**
1007 * @return [Expression]s from <code>operands</code> which are completely cover ed by given
1008 * [SourceRange]. Range should start and end between given [Expression ]s.
1009 */
1010 static List<Expression> _getOperandsForSourceRange(List<Expression> operands, SourceRange range) {
1011 assert(!operands.isEmpty);
1012 List<Expression> subOperands = [];
1013 // track range enter/exit
1014 bool entered = false;
1015 bool exited = false;
1016 // may be range starts before or on first operand
1017 if (range.offset <= operands[0].offset) {
1018 entered = true;
1019 }
1020 // iterate over gaps between operands
1021 for (int i = 0; i < operands.length - 1; i++) {
1022 Expression operand = operands[i];
1023 Expression nextOperand = operands[i + 1];
1024 SourceRange inclusiveGap = rangeEndStart(operand, nextOperand).getMoveEnd( 1);
1025 // add operand, if already entered range
1026 if (entered) {
1027 subOperands.add(operand);
1028 // may be last operand in range
1029 if (range.endsIn(inclusiveGap)) {
1030 exited = true;
1031 }
1032 } else {
1033 // may be first operand in range
1034 if (range.startsIn(inclusiveGap)) {
1035 entered = true;
1036 }
1037 }
1038 }
1039 // check if last operand is in range
1040 Expression lastGroupMember = operands[operands.length - 1];
1041 if (range.end == lastGroupMember.end) {
1042 subOperands.add(lastGroupMember);
1043 exited = true;
1044 }
1045 // we expect that range covers only given operands
1046 if (!exited) {
1047 return [];
1048 }
1049 // done
1050 return subOperands;
1051 }
1052
1053 /**
1054 * @return all operands of the given [BinaryExpression] and its children with the same
1055 * operator.
1056 */
1057 static List<Expression> _getOperandsInOrderFor(BinaryExpression groupRoot) {
1058 List<Expression> operands = [];
1059 TokenType groupOperatorType = groupRoot.operator.type;
1060 groupRoot.accept(new _OrderedOperandsVisitor(groupOperatorType, operands));
1061 return operands;
1062 }
841 } 1063 }
842 1064
843 1065
844 /** 1066 /**
845 * Describes where to insert new directive or top-level declaration. 1067 * Describes where to insert new directive or top-level declaration.
846 */ 1068 */
847 class CorrectionUtils_InsertDesc { 1069 class CorrectionUtils_InsertDesc {
848 int offset = 0; 1070 int offset = 0;
849 String prefix = ""; 1071 String prefix = "";
850 String suffix = ""; 1072 String suffix = "";
851 } 1073 }
852 1074
853 1075
854 /** 1076 /**
1077 * Utilities to work with [Token]s.
1078 */
1079 class TokenUtils {
1080 /**
1081 * @return the first [KeywordToken] with given [Keyword], may be <code>null</c ode> if
1082 * not found.
1083 */
1084 static KeywordToken findKeywordToken(List<Token> tokens, Keyword keyword) {
1085 for (Token token in tokens) {
1086 if (token is KeywordToken) {
1087 KeywordToken keywordToken = token;
1088 if (keywordToken.keyword == keyword) {
1089 return keywordToken;
1090 }
1091 }
1092 }
1093 return null;
1094 }
1095
1096 /**
1097 * @return the first [Token] with given [TokenType], may be <code>null</code> if not
1098 * found.
1099 */
1100 static Token findToken(List<Token> tokens, TokenType type) {
1101 for (Token token in tokens) {
1102 if (token.type == type) {
1103 return token;
1104 }
1105 }
1106 return null;
1107 }
1108
1109 /**
1110 * @return [Token]s of the given Dart source, not <code>null</code>, may be em pty if no
1111 * tokens or some exception happens.
1112 */
1113 static List<Token> getTokens(String s) {
1114 try {
1115 List<Token> tokens = [];
1116 Scanner scanner = new Scanner(null, new CharSequenceReader(s), null);
1117 Token token = scanner.tokenize();
1118 while (token.type != TokenType.EOF) {
1119 tokens.add(token);
1120 token = token.next;
1121 }
1122 return tokens;
1123 } catch (e) {
1124 return [];
1125 }
1126 }
1127
1128 /**
1129 * @return <code>true</code> if given [Token]s contain only single [Token] wit h given
1130 * [TokenType].
1131 */
1132 static bool hasOnly(List<Token> tokens, TokenType type) =>
1133 tokens.length == 1 && tokens[0].type == type;
1134 }
1135
1136 /**
855 * A container with a source and its precedence. 1137 * A container with a source and its precedence.
856 */ 1138 */
857 class _InvertedCondition { 1139 class _InvertedCondition {
858 final int _precedence; 1140 final int _precedence;
859 1141
860 final String _source; 1142 final String _source;
861 1143
862 _InvertedCondition(this._precedence, this._source); 1144 _InvertedCondition(this._precedence, this._source);
863 1145
864 static _InvertedCondition _binary(int precedence, _InvertedCondition left, 1146 static _InvertedCondition _binary(int precedence, _InvertedCondition left,
(...skipping 21 matching lines...) Expand all
886 int newOperatorPrecedence) { 1168 int newOperatorPrecedence) {
887 if (expr._precedence < newOperatorPrecedence) { 1169 if (expr._precedence < newOperatorPrecedence) {
888 return "(${expr._source})"; 1170 return "(${expr._source})";
889 } 1171 }
890 return expr._source; 1172 return expr._source;
891 } 1173 }
892 1174
893 static _InvertedCondition _simple(String source) => 1175 static _InvertedCondition _simple(String source) =>
894 new _InvertedCondition(2147483647, source); 1176 new _InvertedCondition(2147483647, source);
895 } 1177 }
1178
1179
1180 class _OrderedOperandsVisitor extends GeneralizingAstVisitor {
1181 final TokenType groupOperatorType;
1182 final List<Expression> operands;
1183
1184 _OrderedOperandsVisitor(this.groupOperatorType, this.operands);
1185
1186 @override
1187 Object visitExpression(Expression node) {
1188 if (node is BinaryExpression && node.operator.type == groupOperatorType) {
1189 return super.visitNode(node);
1190 }
1191 operands.add(node);
1192 return null;
1193 }
1194 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698