| 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) {
|
| + 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;
|
| + }
|
| +}
|
|
|