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

Unified 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: 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analysis_server/lib/src/services/correction/util.dart
diff --git a/pkg/analysis_server/lib/src/services/correction/util.dart b/pkg/analysis_server/lib/src/services/correction/util.dart
index 2023d2545d4e59fbabacfbdc47d7cbf84612a01c..1c4b26c840f4a4466be54ac95f7c41a70f1809bb 100644
--- a/pkg/analysis_server/lib/src/services/correction/util.dart
+++ b/pkg/analysis_server/lib/src/services/correction/util.dart
@@ -4,6 +4,8 @@
library services.src.correction.util;
+import 'dart:math';
+
import 'package:analysis_server/src/protocol2.dart' show SourceEdit;
import 'package:analysis_server/src/services/correction/source_range.dart';
import 'package:analysis_server/src/services/correction/strings.dart';
@@ -16,6 +18,20 @@ import 'package:analyzer/src/generated/source.dart';
/**
+ * @return <code>true</code> if given [List]s are identical at given position.
+ */
+bool allListsIdentical(List<List> lists, int position) {
+ Object element = lists[0][position];
+ for (List list in lists) {
+ if (list[position] != element) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
* TODO(scheglov) replace with nodes once there will be [CompilationUnit#getComments].
*
* Returns [SourceRange]s of all comments in [unit].
@@ -96,6 +112,27 @@ ExecutableElement getEnclosingExecutableElement(AstNode node) {
return null;
}
+
+/**
+ * @return the enclosing executable [AstNode].
+ */
+ AstNode getEnclosingExecutableNode(AstNode node) {
+ while (node != null) {
+ if (node is FunctionDeclaration) {
+ return node;
+ }
+ if (node is ConstructorDeclaration) {
+ return node;
+ }
+ if (node is MethodDeclaration) {
+ return node;
+ }
+ node = node.parent;
+ }
+ return null;
+}
+
+
/**
* Returns [getExpressionPrecedence] for the parent of [node],
* or `0` if the parent node is [ParenthesizedExpression].
@@ -110,6 +147,7 @@ int getExpressionParentPrecedence(AstNode node) {
return getExpressionPrecedence(parent);
}
+
/**
* Returns the precedence of [node] it is an [Expression], negative otherwise.
*/
@@ -120,6 +158,7 @@ int getExpressionPrecedence(AstNode node) {
return -1000;
}
+
/**
* Returns the namespace of the given [ImportElement].
*/
@@ -129,6 +168,58 @@ Map<String, Element> getImportNamespace(ImportElement imp) {
return namespace.definedNames;
}
+/**
+ * @return the nearest common ancestor [AstNode] of the given [AstNode]s.
+ */
+AstNode getNearestCommonAncestor(List<AstNode> nodes) {
+ // may be no nodes
+ if (nodes.isEmpty) {
+ return null;
+ }
+ // prepare parents
+ List<List<AstNode>> parents = [];
+ for (AstNode node in nodes) {
+ parents.add(getParents(node));
+ }
+ // find min length
+ int minLength = 1 << 20;
+ for (List<AstNode> parentList in parents) {
+ minLength = min(minLength, parentList.length);
+ }
+ // find deepest parent
+ int i = 0;
+ for (; i < minLength; i++) {
+ if (!allListsIdentical(parents, i)) {
+ break;
+ }
+ }
+ return parents[0][i - 1];
+}
+
+/**
+ * @return parent [AstNode]s from [CompilationUnit] (at index "0") to the given one.
+ */
+List<AstNode> getParents(AstNode node) {
+ // prepare number of parents
+ int numParents = 0;
+ {
+ AstNode current = node.parent;
+ while (current != null) {
+ numParents++;
+ current = current.parent;
+ }
+ }
+ // fill array of parents
+ List<AstNode> parents = new List<AstNode>(numParents);
+ AstNode current = node.parent;
+ int index = numParents;
+ while (current != null) {
+ parents[--index] = current;
+ current = current.parent;
+ }
+ return parents;
+}
+
/**
* If given [AstNode] is name of qualified property extraction, returns target from which
@@ -674,6 +765,19 @@ class CorrectionUtils {
_invertCondition0(expression)._source;
/**
+ * @return <code>true</code> if selection range contains only whitespace or comments
+ */
+ bool isJustWhitespaceOrComment(SourceRange range) {
+ String trimmedText = getRangeText(range).trim();
+ // may be whitespace
+ if (trimmedText.isEmpty) {
+ return true;
+ }
+ // may be comment
+ return TokenUtils.getTokens(trimmedText).isEmpty;
+ }
+
+ /**
* Returns the source with indentation changed from [oldIndent] to
* [newIndent], keeping indentation of lines relative to each other.
*/
@@ -735,6 +839,40 @@ class CorrectionUtils {
}
/**
+ * @return <code>true</code> if "selection" covers "node" and there are any non-whitespace tokens
+ * between "selection" and "node" start/end.
+ */
+ bool selectionIncludesNonWhitespaceOutsideNode(SourceRange selection,
+ AstNode node) {
+ return _selectionIncludesNonWhitespaceOutsideRange(
+ selection,
+ rangeNode(node));
+ }
+
+ /**
+ * @return <code>true</code> if given range of [BinaryExpression] can be extracted.
+ */
+ bool validateBinaryExpressionRange(BinaryExpression binaryExpression, SourceRange range) {
+ // only parts of associative expression are safe to extract
+ if (!binaryExpression.operator.type.isAssociativeOperator) {
+ return false;
+ }
+ // prepare selected operands
+ List<Expression> operands = _getOperandsInOrderFor(binaryExpression);
+ List<Expression> subOperands = _getOperandsForSourceRange(operands, range);
+ // if empty, then something wrong with selection
+ if (subOperands.isEmpty) {
+ return false;
+ }
+ // may be some punctuation included into selection - operators, braces, etc
+ if (_selectionIncludesNonWhitespaceOutsideOperands(range, subOperands)) {
+ return false;
+ }
+ // OK
+ return true;
+ }
+
+ /**
* @return the [ImportElement] used to import given [Element] into [library].
* May be `null` if was not imported, i.e. declared in the same library.
*/
@@ -838,6 +976,90 @@ class CorrectionUtils {
}
return _InvertedCondition._simple(getNodeText(expression));
}
+
+ bool _selectionIncludesNonWhitespaceOutsideOperands(SourceRange selection, List<Expression> operands) {
+ return _selectionIncludesNonWhitespaceOutsideRange(selection, rangeNodes(operands));
+ }
+
+ /**
+ * @return <code>true</code> if "selection" covers "range" and there are any non-whitespace tokens
+ * between "selection" and "range" start/end.
+ */
+ bool _selectionIncludesNonWhitespaceOutsideRange(SourceRange selection,
+ SourceRange range) {
+ // selection should cover range
+ if (!selection.covers(range)) {
+ return false;
+ }
+ // non-whitespace between selection start and range start
+ if (!isJustWhitespaceOrComment(rangeStartStart(selection, range))) {
+ return true;
+ }
+ // non-whitespace after range
+ if (!isJustWhitespaceOrComment(rangeEndEnd(range, selection))) {
+ return true;
+ }
+ // only whitespace in selection around range
+ return false;
+ }
+
+ /**
+ * @return [Expression]s from <code>operands</code> which are completely covered by given
+ * [SourceRange]. Range should start and end between given [Expression]s.
+ */
+ static List<Expression> _getOperandsForSourceRange(List<Expression> operands, SourceRange range) {
+ assert(!operands.isEmpty);
+ List<Expression> subOperands = [];
+ // track range enter/exit
+ bool entered = false;
+ bool exited = false;
+ // may be range starts before or on first operand
+ if (range.offset <= operands[0].offset) {
+ entered = true;
+ }
+ // iterate over gaps between operands
+ for (int i = 0; i < operands.length - 1; i++) {
+ Expression operand = operands[i];
+ Expression nextOperand = operands[i + 1];
+ SourceRange inclusiveGap = rangeEndStart(operand, nextOperand).getMoveEnd(1);
+ // add operand, if already entered range
+ if (entered) {
+ subOperands.add(operand);
+ // may be last operand in range
+ if (range.endsIn(inclusiveGap)) {
+ exited = true;
+ }
+ } else {
+ // may be first operand in range
+ if (range.startsIn(inclusiveGap)) {
+ entered = true;
+ }
+ }
+ }
+ // check if last operand is in range
+ Expression lastGroupMember = operands[operands.length - 1];
+ if (range.end == lastGroupMember.end) {
+ subOperands.add(lastGroupMember);
+ exited = true;
+ }
+ // we expect that range covers only given operands
+ if (!exited) {
+ return [];
+ }
+ // done
+ return subOperands;
+ }
+
+ /**
+ * @return all operands of the given [BinaryExpression] and its children with the same
+ * operator.
+ */
+ static List<Expression> _getOperandsInOrderFor(BinaryExpression groupRoot) {
+ List<Expression> operands = [];
+ TokenType groupOperatorType = groupRoot.operator.type;
+ groupRoot.accept(new _OrderedOperandsVisitor(groupOperatorType, operands));
+ return operands;
+ }
}
@@ -852,6 +1074,66 @@ class CorrectionUtils_InsertDesc {
/**
+ * Utilities to work with [Token]s.
+ */
+class TokenUtils {
+ /**
+ * @return the first [KeywordToken] with given [Keyword], may be <code>null</code> if
+ * not found.
+ */
+ static KeywordToken findKeywordToken(List<Token> tokens, Keyword keyword) {
+ for (Token token in tokens) {
+ if (token is KeywordToken) {
+ KeywordToken keywordToken = token;
+ if (keywordToken.keyword == keyword) {
+ return keywordToken;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the first [Token] with given [TokenType], may be <code>null</code> if not
+ * found.
+ */
+ static Token findToken(List<Token> tokens, TokenType type) {
+ for (Token token in tokens) {
+ if (token.type == type) {
+ return token;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return [Token]s of the given Dart source, not <code>null</code>, may be empty if no
+ * tokens or some exception happens.
+ */
+ 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.
+ try {
+ List<Token> tokens = [];
+ Scanner scanner = new Scanner(null, new CharSequenceReader(s), null);
+ Token token = scanner.tokenize();
+ while (token.type != TokenType.EOF) {
+ tokens.add(token);
+ token = token.next;
+ }
+ return tokens;
+ } catch (e) {
+ return [];
+ }
+ }
+
+ /**
+ * @return <code>true</code> if given [Token]s contain only single [Token] with given
+ * [TokenType].
+ */
+ static bool hasOnly(List<Token> tokens, TokenType type) =>
+ tokens.length == 1 && tokens[0].type == type;
+}
+
+/**
* A container with a source and its precedence.
*/
class _InvertedCondition {
@@ -893,3 +1175,20 @@ class _InvertedCondition {
static _InvertedCondition _simple(String source) =>
new _InvertedCondition(2147483647, source);
}
+
+
+class _OrderedOperandsVisitor extends GeneralizingAstVisitor {
+ final TokenType groupOperatorType;
+ final List<Expression> operands;
+
+ _OrderedOperandsVisitor(this.groupOperatorType, this.operands);
+
+ @override
+ Object visitExpression(Expression node) {
+ if (node is BinaryExpression && node.operator.type == groupOperatorType) {
+ return super.visitNode(node);
+ }
+ operands.add(node);
+ return null;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698