Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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) { | |
|
Brian Wilkerson
2014/08/20 22:11:16
This seems slow, especially for large files. Do we
scheglov
2014/08/20 22:19:21
Acknowledged.
| |
| 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 Loading... | |
| 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 } | |
| OLD | NEW |