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

Unified Diff: pkg/analysis_services/lib/src/generated/util.dart

Issue 250823006: Translate parts of engine.services project. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Move to pkg/analysis_services/ Created 6 years, 8 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
« no previous file with comments | « pkg/analysis_services/lib/src/generated/stubs.dart ('k') | pkg/analysis_services/pubspec.yaml » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/analysis_services/lib/src/generated/util.dart
diff --git a/pkg/analysis_services/lib/src/generated/util.dart b/pkg/analysis_services/lib/src/generated/util.dart
new file mode 100644
index 0000000000000000000000000000000000000000..49c5394d5415b6d48fc0bbbe0f2134a309f22977
--- /dev/null
+++ b/pkg/analysis_services/lib/src/generated/util.dart
@@ -0,0 +1,2437 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This code was auto-generated, is not intended to be edited, and is subject to
+// significant change. Please see the README file for more information.
+
+library services.util;
+
+import 'dart:collection';
+import 'package:analyzer/src/generated/java_core.dart' hide StringUtils;
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/error.dart';
+import 'package:analyzer/src/generated/resolver.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/generated/scanner.dart';
+import 'change.dart';
+import 'proposal.dart';
+import 'status.dart';
+import 'stubs.dart';
+
+/**
+ * Context for which assistance should be provided.
+ */
+class AssistContext {
+ final SearchEngine searchEngine;
+
+ final AnalysisContext analysisContext;
+
+ final String analysisContextId;
+
+ final Source source;
+
+ final CompilationUnit compilationUnit;
+
+ final int selectionOffset;
+
+ final int selectionLength;
+
+ AstNode _coveredNode;
+
+ AstNode _coveringNode;
+
+ Element _coveredElement;
+
+ bool _coveredElementFound = false;
+
+ AssistContext.con1(this.searchEngine, this.analysisContext, this.analysisContextId, this.source, this.compilationUnit, this.selectionOffset, this.selectionLength);
+
+ AssistContext.con2(SearchEngine searchEngine, AnalysisContext analysisContext, String analysisContextId, Source source, CompilationUnit compilationUnit, SourceRange selectionRange) : this.con1(searchEngine, analysisContext, analysisContextId, source, compilationUnit, selectionRange.offset, selectionRange.length);
+
+ /**
+ * @return the resolved [CompilationUnitElement] of the [Source].
+ */
+ CompilationUnitElement get compilationUnitElement => compilationUnit.element;
+
+ /**
+ * @return the [Element] of the [coveredNode], may be <code>null</code>.
+ */
+ Element get coveredElement {
+ if (!_coveredElementFound) {
+ _coveredElementFound = true;
+ AstNode coveredNode = this.coveredNode;
+ if (coveredNode == null) {
+ return null;
+ }
+ _coveredElement = ElementLocator.locateWithOffset(coveredNode, selectionOffset);
+ }
+ return _coveredElement;
+ }
+
+ /**
+ * @return the [AstNode] that is covered by the selection.
+ */
+ AstNode get coveredNode {
+ if (_coveredNode == null) {
+ NodeLocator locator = new NodeLocator.con2(selectionOffset, selectionOffset);
+ _coveredNode = locator.searchWithin(compilationUnit);
+ }
+ return _coveredNode;
+ }
+
+ /**
+ * @return the ASTNode that covers the selection.
+ */
+ AstNode get coveringNode {
+ if (_coveringNode == null) {
+ NodeLocator locator = new NodeLocator.con2(selectionOffset, selectionOffset + selectionLength);
+ _coveringNode = locator.searchWithin(compilationUnit);
+ }
+ return _coveringNode;
+ }
+
+ /**
+ * @return the errors associated with the [Source].
+ */
+ List<AnalysisError> get errors {
+ Source source = this.source;
+ if (analysisContext == null || source == null) {
+ return AnalysisError.NO_ERRORS;
+ }
+ return analysisContext.getErrors(source).errors;
+ }
+
+ /**
+ * @return the [SourceRange] of the selection.
+ */
+ SourceRange get selectionRange => new SourceRange(selectionOffset, selectionLength);
+}
+
+/**
+ * Utilities for analyzing [CompilationUnit], its parts and source.
+ */
+class CorrectionUtils {
+ /**
+ * If `true` then [addEdit] validates that
+ * [Edit] replaces correct part of the [Source].
+ */
+ static bool _DEBUG_VALIDATE_EDITS = true;
+
+ static List<String> _KNOWN_METHOD_NAME_PREFIXES = ["get", "is", "to"];
+
+ /**
+ * Validates that the [Edit] replaces the expected part of the [Source] and adds this
+ * [Edit] to the [SourceChange].
+ */
+ static void addEdit(AnalysisContext context, SourceChange change, String description, String expected, Edit edit) {
+ if (_DEBUG_VALIDATE_EDITS) {
+ Source source = change.source;
+ String sourceContent = getSourceContent(context, source);
+ // prepare range
+ int beginIndex = edit.offset;
+ int endIndex = beginIndex + edit.length;
+ int sourceLength = sourceContent.length;
+ if (beginIndex >= sourceLength || endIndex >= sourceLength) {
+ throw new IllegalStateException("${source} has ${sourceLength} characters but ${beginIndex} to ${endIndex} requested.\n\nTry to use Tools | Reanalyze Sources.");
+ }
+ // check that range has expected content
+ String rangeContent = sourceContent.substring(beginIndex, endIndex);
+ if (rangeContent != expected) {
+ throw new IllegalStateException("${source} expected |${expected}| at ${beginIndex} to ${endIndex} but |${rangeContent}| found.\n\nTry to use Tools | Reanalyze Sources.");
+ }
+ }
+ // do add the Edit
+ change.addEdit(edit, description);
+ }
+
+ /**
+ * @return <code>true</code> if given [List]s are equals at given position.
+ */
+ static bool allListsEqual(List<List> lists, int position) {
+ Object element = lists[0][position];
+ for (List list in lists) {
+ if (!identical(list[position], element)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return the updated [String] with applied [Edit]s.
+ */
+ static String applyReplaceEdits(String s, List<Edit> edits) {
+ // sort edits
+ edits = [];
+ edits.sort((Edit o1, Edit o2) => o1.offset - o2.offset);
+ // apply edits
+ int delta = 0;
+ for (Edit edit in edits) {
+ int editOffset = edit.offset + delta;
+ String beforeEdit = s.substring(0, editOffset);
+ String afterEdit = s.substring(editOffset + edit.length);
+ s = "${beforeEdit}${edit.replacement}${afterEdit}";
+ delta += getDeltaOffset(edit);
+ }
+ // done
+ return s;
+ }
+
+ /**
+ * @return <code>true</code> if given [SourceRange] covers given [AstNode].
+ */
+ static bool covers(SourceRange r, AstNode node) {
+ SourceRange nodeRange = SourceRangeFactory.rangeNode(node);
+ return r.covers(nodeRange);
+ }
+
+ /**
+ * @return all direct children of the given [Element].
+ */
+ static List<Element> getChildren(Element parent) => getChildren2(parent, null);
+
+ /**
+ * @param name the required name of children; may be <code>null</code> to get children with any
+ * name.
+ * @return all direct children of the given [Element], with given name.
+ */
+ static List<Element> getChildren2(Element parent, String name) {
+ List<Element> children = [];
+ parent.accept(new GeneralizingElementVisitor_CorrectionUtils_getChildren(parent, name, children));
+ return children;
+ }
+
+ static String getDefaultValueCode(DartType type) {
+ if (type != null) {
+ String typeName = type.displayName;
+ if (typeName == "bool") {
+ return "false";
+ }
+ if (typeName == "int") {
+ return "0";
+ }
+ if (typeName == "double") {
+ return "0.0";
+ }
+ if (typeName == "String") {
+ return "''";
+ }
+ }
+ // no better guess
+ return "null";
+ }
+
+ /**
+ * @return the number of characters this [Edit] will move offsets after its range.
+ */
+ static int getDeltaOffset(Edit edit) => edit.replacement.length - edit.length;
+
+ /**
+ * @return the name of the [Element] kind.
+ */
+ static String getElementKindName(Element element) {
+ ElementKind kind = element.kind;
+ return getElementKindName2(kind);
+ }
+
+ /**
+ * @return the display name of the [ElementKind].
+ */
+ static String getElementKindName2(ElementKind kind) => kind.displayName;
+
+ /**
+ * @return the human name of the [Element].
+ */
+ static String getElementQualifiedName(Element element) {
+ ElementKind kind = element.kind;
+ while (true) {
+ if (kind == ElementKind.FIELD || kind == ElementKind.METHOD) {
+ return "${element.enclosingElement.displayName}.${element.displayName}";
+ } else {
+ return element.displayName;
+ }
+ break;
+ }
+ }
+
+ /**
+ * @return the [ExecutableElement] of the enclosing executable [AstNode].
+ */
+ static ExecutableElement getEnclosingExecutableElement(AstNode node) {
+ while (node != null) {
+ if (node is FunctionDeclaration) {
+ return node.element;
+ }
+ if (node is ConstructorDeclaration) {
+ return node.element;
+ }
+ if (node is MethodDeclaration) {
+ return node.element;
+ }
+ node = node.parent;
+ }
+ return null;
+ }
+
+ /**
+ * @return the enclosing executable [AstNode].
+ */
+ static 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;
+ }
+
+ /**
+ * @return [Element] exported from the given [LibraryElement].
+ */
+ static Element getExportedElement(LibraryElement library, String name) {
+ if (library == null) {
+ return null;
+ }
+ return getExportNamespace2(library)[name];
+ }
+
+ /**
+ * TODO(scheglov) may be replace with some API for this
+ *
+ * @return the namespace of the given [ExportElement].
+ */
+ static Map<String, Element> getExportNamespace(ExportElement exp) {
+ Namespace namespace = new NamespaceBuilder().createExportNamespaceForDirective(exp);
+ return namespace.definedNames;
+ }
+
+ /**
+ * TODO(scheglov) may be replace with some API for this
+ *
+ * @return the export namespace of the given [LibraryElement].
+ */
+ static Map<String, Element> getExportNamespace2(LibraryElement library) {
+ Namespace namespace = new NamespaceBuilder().createExportNamespaceForLibrary(library);
+ return namespace.definedNames;
+ }
+
+ /**
+ * @return [getExpressionPrecedence] for parent node, or `0` if parent node
+ * is [ParenthesizedExpression]. The reason is that `(expr)` is always
+ * executed after `expr`.
+ */
+ static int getExpressionParentPrecedence(AstNode node) {
+ AstNode parent = node.parent;
+ if (parent is ParenthesizedExpression) {
+ return 0;
+ }
+ return getExpressionPrecedence(parent);
+ }
+
+ /**
+ * @return the precedence of the given node - result of [Expression#getPrecedence] if an
+ * [Expression], negative otherwise.
+ */
+ static int getExpressionPrecedence(AstNode node) {
+ if (node is Expression) {
+ return node.precedence;
+ }
+ return -1000;
+ }
+
+ /**
+ * TODO(scheglov) may be replace with some API for this
+ *
+ * @return the namespace of the given [ImportElement].
+ */
+ static Map<String, Element> getImportNamespace(ImportElement imp) {
+ Namespace namespace = new NamespaceBuilder().createImportNamespaceForDirective(imp);
+ return namespace.definedNames;
+ }
+
+ /**
+ * @return all [CompilationUnitElement] the given [LibraryElement] consists of.
+ */
+ static List<CompilationUnitElement> getLibraryUnits(LibraryElement library) {
+ List<CompilationUnitElement> units = [];
+ units.add(library.definingCompilationUnit);
+ units.addAll(library.parts);
+ return units;
+ }
+
+ /**
+ * @return the line prefix from the given source, i.e. basically just whitespace prefix of the
+ * given [String].
+ */
+ static String getLinesPrefix(String lines) {
+ int index = 0;
+ while (index < lines.length) {
+ int c = lines.codeUnitAt(index);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ index++;
+ }
+ return lines.substring(0, index);
+ }
+
+ /**
+ * @return the [LocalVariableElement] or [ParameterElement] if given
+ * [SimpleIdentifier] is the reference to local variable or parameter, or
+ * <code>null</code> in the other case.
+ */
+ static VariableElement getLocalOrParameterVariableElement(SimpleIdentifier node) {
+ Element element = node.staticElement;
+ if (element is LocalVariableElement) {
+ return element;
+ }
+ if (element is ParameterElement) {
+ return element;
+ }
+ return null;
+ }
+
+ /**
+ * @return the [LocalVariableElement] if given [SimpleIdentifier] is the reference to
+ * local variable, or <code>null</code> in the other case.
+ */
+ static LocalVariableElement getLocalVariableElement(SimpleIdentifier node) {
+ Element element = node.staticElement;
+ if (element is LocalVariableElement) {
+ return element;
+ }
+ return null;
+ }
+
+ /**
+ * @return the nearest common ancestor [AstNode] of the given [AstNode]s.
+ */
+ static 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 = 2147483647;
+ for (List<AstNode> parentList in parents) {
+ minLength = Math.min(minLength, parentList.length);
+ }
+ // find deepest parent
+ int i = 0;
+ for (; i < minLength; i++) {
+ if (!allListsEqual(parents, i)) {
+ break;
+ }
+ }
+ return parents[0][i - 1];
+ }
+
+ /**
+ * @return the [Expression] qualified if given node is name part of a [PropertyAccess]
+ * or [PrefixedIdentifier]. May be <code>null</code>.
+ */
+ static Expression getNodeQualifier(SimpleIdentifier node) {
+ AstNode parent = node.parent;
+ if (parent is PropertyAccess) {
+ PropertyAccess propertyAccess = parent;
+ if (identical(propertyAccess.propertyName, node)) {
+ return propertyAccess.target;
+ }
+ }
+ if (parent is PrefixedIdentifier) {
+ PrefixedIdentifier prefixed = parent;
+ if (identical(prefixed.identifier, node)) {
+ return prefixed.prefix;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the [ParameterElement] if given [SimpleIdentifier] is the reference to
+ * parameter, or <code>null</code> in the other case.
+ */
+ static ParameterElement getParameterElement(SimpleIdentifier node) {
+ Element element = node.staticElement;
+ if (element is ParameterElement) {
+ return element;
+ }
+ return null;
+ }
+
+ /**
+ * @return the precedence of the given [Expression] parent. May be `-1` no operator.
+ * @see #getPrecedence(Expression)
+ */
+ static int getParentPrecedence(Expression expression) {
+ AstNode parent = expression.parent;
+ if (parent is Expression) {
+ return getPrecedence(parent);
+ }
+ return -1;
+ }
+
+ /**
+ * @return parent [AstNode]s from [CompilationUnit] (at index "0") to the given one.
+ */
+ static 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 JavaArrays.asList(parents);
+ }
+
+ /**
+ * @return the precedence of the given [Expression] operator. May be
+ * `Integer#MAX_VALUE` if not an operator.
+ */
+ static int getPrecedence(Expression expression) {
+ if (expression is BinaryExpression) {
+ BinaryExpression binaryExpression = expression;
+ return binaryExpression.operator.type.precedence;
+ }
+ if (expression is PrefixExpression) {
+ PrefixExpression prefixExpression = expression;
+ return prefixExpression.operator.type.precedence;
+ }
+ if (expression is PostfixExpression) {
+ PostfixExpression postfixExpression = expression;
+ return postfixExpression.operator.type.precedence;
+ }
+ return 2147483647;
+ }
+
+ /**
+ * @return the [PropertyAccessorElement] if given [SimpleIdentifier] is the reference
+ * to property, or <code>null</code> in the other case.
+ */
+ static PropertyAccessorElement getPropertyAccessorElement(SimpleIdentifier node) {
+ Element element = node.staticElement;
+ if (element is PropertyAccessorElement) {
+ return element;
+ }
+ return null;
+ }
+
+ /**
+ * If given [AstNode] is name of qualified property extraction, returns target from which
+ * this property is extracted. Otherwise `null`.
+ */
+ static Expression getQualifiedPropertyTarget(AstNode node) {
+ AstNode parent = node.parent;
+ if (parent is PrefixedIdentifier) {
+ PrefixedIdentifier prefixed = parent;
+ if (identical(prefixed.identifier, node)) {
+ return parent.prefix;
+ }
+ }
+ if (parent is PropertyAccess) {
+ PropertyAccess access = parent;
+ if (identical(access.propertyName, node)) {
+ return access.realTarget;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the name of the file which corresponds to the name of the class according to the style
+ * guide. However class does not have to be in this file.
+ */
+ static String getRecommentedFileNameForClass(String className) {
+ int len = className.length;
+ JavaStringBuilder sb = new JavaStringBuilder();
+ bool prevWasUpper = false;
+ for (int i = 0; i < len; i++) {
+ int c = className.codeUnitAt(i);
+ if (Character.isUpperCase(c)) {
+ bool nextIsUpper = i < len - 1 && Character.isUpperCase(className.codeUnitAt(i + 1));
+ if (i == 0) {
+ } else if (prevWasUpper) {
+ // HTTPServer
+ // ^
+ if (!nextIsUpper) {
+ sb.appendChar(0x5F);
+ }
+ } else {
+ // HttpServer
+ // ^
+ sb.appendChar(0x5F);
+ }
+ prevWasUpper = true;
+ c = Character.toLowerCase(c);
+ } else {
+ prevWasUpper = false;
+ }
+ sb.appendChar(c);
+ }
+ sb.append(".dart");
+ String fileName = sb.toString();
+ return fileName;
+ }
+
+ /**
+ * @return given [Statement] if not [Block], first child [Statement] if
+ * [Block], or <code>null</code> if more than one child.
+ */
+ static Statement getSingleStatement(Statement statement) {
+ if (statement is Block) {
+ List<Statement> blockStatements = statement.statements;
+ if (blockStatements.length != 1) {
+ return null;
+ }
+ return blockStatements[0];
+ }
+ return statement;
+ }
+
+ /**
+ * @return the [String] content of the given [Source].
+ */
+ static String getSourceContent(AnalysisContext context, Source source) => context.getContents(source).data.toString();
+
+ /**
+ * @return given [Statement] if not [Block], all children [Statement]s if
+ * [Block].
+ */
+ static List<Statement> getStatements(Statement statement) {
+ if (statement is Block) {
+ return statement.statements;
+ }
+ return [];
+ }
+
+ /**
+ * @return all top-level elements declared in the given [LibraryElement].
+ */
+ static List<Element> getTopLevelElements(LibraryElement library) {
+ List<Element> elements = [];
+ List<CompilationUnitElement> units = getLibraryUnits(library);
+ for (CompilationUnitElement unit in units) {
+ elements.addAll(unit.functions);
+ elements.addAll(unit.functionTypeAliases);
+ elements.addAll(unit.types);
+ elements.addAll(unit.topLevelVariables);
+ }
+ return elements;
+ }
+
+ /**
+ * @return the possible names for variable with initializer of the given [StringLiteral].
+ */
+ static List<String> getVariableNameSuggestions(String text, Set<String> excluded) {
+ // filter out everything except of letters and white spaces
+ {
+ JavaStringBuilder sb = new JavaStringBuilder();
+ for (int i = 0; i < text.length; i++) {
+ int c = text.codeUnitAt(i);
+ if (Character.isLetter(c) || Character.isWhitespace(c)) {
+ sb.appendChar(c);
+ }
+ }
+ text = sb.toString();
+ }
+ // make single camel-case text
+ {
+ List<String> words = StringUtils.split(text);
+ JavaStringBuilder sb = new JavaStringBuilder();
+ for (int i = 0; i < words.length; i++) {
+ String word = words[i];
+ if (i > 0) {
+ word = StringUtils.capitalize(word);
+ }
+ sb.append(word);
+ }
+ text = sb.toString();
+ }
+ // split camel-case into separate suggested names
+ Set<String> res = new LinkedHashSet();
+ _addAll(excluded, res, _getVariableNameSuggestions(text));
+ return new List.from(res);
+ }
+
+ /**
+ * @return the possible names for variable with given expected type and expression.
+ */
+ static List<String> getVariableNameSuggestions2(DartType expectedType, Expression assignedExpression, Set<String> excluded) {
+ Set<String> res = new LinkedHashSet();
+ // use expression
+ if (assignedExpression != null) {
+ String nameFromExpression = _getBaseNameFromExpression(assignedExpression);
+ if (nameFromExpression != null) {
+ nameFromExpression = StringUtils.removeStart(nameFromExpression, "_");
+ _addAll(excluded, res, _getVariableNameSuggestions(nameFromExpression));
+ }
+ String nameFromParent = _getBaseNameFromLocationInParent(assignedExpression);
+ if (nameFromParent != null) {
+ _addAll(excluded, res, _getVariableNameSuggestions(nameFromParent));
+ }
+ }
+ // use type
+ if (expectedType != null && !expectedType.isDynamic) {
+ String typeName = expectedType.name;
+ if ("int" == typeName) {
+ _addSingleCharacterName(excluded, res, 0x69);
+ } else if ("double" == typeName) {
+ _addSingleCharacterName(excluded, res, 0x64);
+ } else if ("String" == typeName) {
+ _addSingleCharacterName(excluded, res, 0x73);
+ } else {
+ _addAll(excluded, res, _getVariableNameSuggestions(typeName));
+ }
+ res.remove(typeName);
+ }
+ // done
+ return new List.from(res);
+ }
+
+ /**
+ * @return `true` if the given [Element#getDisplayName] equals to the given name.
+ */
+ static bool hasDisplayName(Element element, String name) {
+ if (element == null) {
+ return false;
+ }
+ String elementDisplayName = element.displayName;
+ return StringUtils.equals(elementDisplayName, name);
+ }
+
+ /**
+ * @return `true` if the given [Element#getName] equals to the given name.
+ */
+ static bool hasName(Element element, String name) {
+ if (element == null) {
+ return false;
+ }
+ String elementName = element.name;
+ return StringUtils.equals(elementName, name);
+ }
+
+ /**
+ * @return `true` if the given [SimpleIdentifier] is the name of the
+ * [NamedExpression].
+ */
+ static bool isNamedExpressionName(SimpleIdentifier node) {
+ AstNode parent = node.parent;
+ if (parent is Label) {
+ Label label = parent;
+ if (identical(label.label, node)) {
+ AstNode parent2 = label.parent;
+ if (parent2 is NamedExpression) {
+ return identical(parent2.name, label);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adds "toAdd" items which are not excluded.
+ */
+ static void _addAll(Set<String> excluded, Set<String> result, Iterable<String> toAdd) {
+ for (String item in toAdd) {
+ // add name based on "item", but not "excluded"
+ for (int suffix = 1;; suffix++) {
+ // prepare name, just "item" or "item2", "item3", etc
+ String name = item;
+ if (suffix > 1) {
+ name += suffix.toString();
+ }
+ // add once found not excluded
+ if (!excluded.contains(name)) {
+ result.add(name);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Add to "result" then given "c" or the first ASCII character after it.
+ */
+ static void _addSingleCharacterName(Set<String> excluded, Set<String> result, int c) {
+ while (c < 0x7A) {
+ String name = new String.fromCharCode(c);
+ // may be done
+ if (!excluded.contains(name)) {
+ result.add(name);
+ break;
+ }
+ // next character
+ c = (c + 1);
+ }
+ }
+
+ static String _getBaseNameFromExpression(Expression expression) {
+ String name = null;
+ // e as Type
+ if (expression is AsExpression) {
+ AsExpression asExpression = expression as AsExpression;
+ expression = asExpression.expression;
+ }
+ // analyze expressions
+ if (expression is SimpleIdentifier) {
+ SimpleIdentifier node = expression;
+ return node.name;
+ } else if (expression is PrefixedIdentifier) {
+ PrefixedIdentifier node = expression;
+ return node.identifier.name;
+ } else if (expression is MethodInvocation) {
+ name = expression.methodName.name;
+ } else if (expression is InstanceCreationExpression) {
+ InstanceCreationExpression creation = expression;
+ ConstructorName constructorName = creation.constructorName;
+ TypeName typeName = constructorName.type;
+ if (typeName != null) {
+ Identifier typeNameIdentifier = typeName.name;
+ // new ClassName()
+ if (typeNameIdentifier is SimpleIdentifier) {
+ return typeNameIdentifier.name;
+ }
+ // new prefix.name();
+ if (typeNameIdentifier is PrefixedIdentifier) {
+ PrefixedIdentifier prefixed = typeNameIdentifier;
+ // new prefix.ClassName()
+ if (prefixed.prefix.staticElement is PrefixElement) {
+ return prefixed.identifier.name;
+ }
+ // new ClassName.constructorName()
+ return prefixed.prefix.name;
+ }
+ }
+ }
+ // strip known prefixes
+ if (name != null) {
+ for (int i = 0; i < _KNOWN_METHOD_NAME_PREFIXES.length; i++) {
+ String curr = _KNOWN_METHOD_NAME_PREFIXES[i];
+ if (name.startsWith(curr)) {
+ if (name == curr) {
+ return null;
+ } else if (Character.isUpperCase(name.codeUnitAt(curr.length))) {
+ return name.substring(curr.length);
+ }
+ }
+ }
+ }
+ // done
+ return name;
+ }
+
+ static String _getBaseNameFromLocationInParent(Expression expression) {
+ // value in named expression
+ if (expression.parent is NamedExpression) {
+ NamedExpression namedExpression = expression.parent as NamedExpression;
+ if (identical(namedExpression.expression, expression)) {
+ return namedExpression.name.label.name;
+ }
+ }
+ // positional argument
+ {
+ ParameterElement parameter = expression.propagatedParameterElement;
+ if (parameter == null) {
+ parameter = expression.staticParameterElement;
+ }
+ if (parameter != null) {
+ return parameter.displayName;
+ }
+ }
+ // unknown
+ return null;
+ }
+
+ /**
+ * @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 = SourceRangeFactory.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 GeneralizingAstVisitor_CorrectionUtils_getOperandsInOrderFor(groupOperatorType, operands));
+ return operands;
+ }
+
+ /**
+ * @return all variants of names by removing leading words by one.
+ */
+ static List<String> _getVariableNameSuggestions(String name) {
+ List<String> result = [];
+ List<String> parts = name.split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])");
+ for (int i = 0; i < parts.length; i++) {
+ String suggestion = "${parts[i].toLowerCase()}${StringUtils.join(parts, "", i + 1, parts.length)}";
+ result.add(suggestion);
+ }
+ return result;
+ }
+
+ /**
+ * Adds enclosing parenthesis if the precedence of the [InvertedCondition] if less than the
+ * precedence of the expression we are going it to use in.
+ */
+ static String _parenthesizeIfRequired(CorrectionUtils_InvertedCondition expr, int newOperatorPrecedence) {
+ if (expr._precedence < newOperatorPrecedence) {
+ return "(${expr._source})";
+ }
+ return expr._source;
+ }
+
+ final CompilationUnit unit;
+
+ LibraryElement _library;
+
+ String _buffer;
+
+ String _endOfLine;
+
+ CorrectionUtils(this.unit) {
+ CompilationUnitElement element = unit.element;
+ this._library = element.library;
+ this._buffer = getSourceContent(element.context, element.source);
+ }
+
+ /**
+ * @return the source of the given [SourceRange] with indentation changed from "oldIndent"
+ * to "newIndent", keeping indentation of the lines relative to each other.
+ */
+ Edit createIndentEdit(SourceRange range, String oldIndent, String newIndent) {
+ String newSource = getIndentSource(range, oldIndent, newIndent);
+ return new Edit(range.offset, range.length, newSource);
+ }
+
+ /**
+ * @return the [AstNode] that encloses the given offset.
+ */
+ AstNode findNode(int offset) => new NodeLocator.con1(offset).searchWithin(unit);
+
+ /**
+ * TODO(scheglov) replace with nodes once there will be [CompilationUnit#getComments].
+ *
+ * @return the [SourceRange]s of all comments in [CompilationUnit].
+ */
+ List<SourceRange> get commentRanges {
+ List<SourceRange> ranges = [];
+ Token token = unit.beginToken;
+ while (token != null && token.type != TokenType.EOF) {
+ Token commentToken = token.precedingComments;
+ while (commentToken != null) {
+ ranges.add(SourceRangeFactory.rangeToken(commentToken));
+ commentToken = commentToken.next;
+ }
+ token = token.next;
+ }
+ return ranges;
+ }
+
+ /**
+ * @return the EOL to use for this [CompilationUnit].
+ */
+ String get endOfLine {
+ if (_endOfLine == null) {
+ if (_buffer.contains("\r\n")) {
+ _endOfLine = "\r\n";
+ } else {
+ _endOfLine = "\n";
+ }
+ }
+ return _endOfLine;
+ }
+
+ /**
+ * @return the default indentation with given level.
+ */
+ String getIndent(int level) => StringUtils.repeat(" ", level);
+
+ /**
+ * @return the source of the given [SourceRange] with indentation changed from "oldIndent"
+ * to "newIndent", keeping indentation of the lines relative to each other.
+ */
+ String getIndentSource(SourceRange range, String oldIndent, String newIndent) {
+ String oldSource = getText3(range);
+ return getIndentSource3(oldSource, oldIndent, newIndent);
+ }
+
+ /**
+ * Indents given source left or right.
+ *
+ * @return the source with changed indentation.
+ */
+ String getIndentSource2(String source, bool right) {
+ JavaStringBuilder sb = new JavaStringBuilder();
+ String indent = getIndent(1);
+ String eol = endOfLine;
+ List<String> lines = StringUtils.splitByWholeSeparatorPreserveAllTokens(source, eol);
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i];
+ // last line, stop if empty
+ if (i == lines.length - 1 && StringUtils.isEmpty(line)) {
+ break;
+ }
+ // update line
+ if (right) {
+ line = "${indent}${line}";
+ } else {
+ line = StringUtils.removeStart(line, indent);
+ }
+ // append line
+ sb.append(line);
+ sb.append(eol);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return the source with indentation changed from "oldIndent" to "newIndent", keeping
+ * indentation of the lines relative to each other.
+ */
+ String getIndentSource3(String source, String oldIndent, String newIndent) {
+ // prepare STRING token ranges
+ List<SourceRange> lineRanges = [];
+ for (Token token in TokenUtils.getTokens(source)) {
+ if (token.type == TokenType.STRING) {
+ lineRanges.add(SourceRangeFactory.rangeToken(token));
+ }
+ }
+ // re-indent lines
+ JavaStringBuilder sb = new JavaStringBuilder();
+ String eol = endOfLine;
+ List<String> lines = StringUtils.splitByWholeSeparatorPreserveAllTokens(source, eol);
+ int lineOffset = 0;
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i];
+ // last line, stop if empty
+ if (i == lines.length - 1 && StringUtils.isEmpty(line)) {
+ break;
+ }
+ // check if "offset" is in one of the String ranges
+ bool inString = false;
+ for (SourceRange lineRange in lineRanges) {
+ inString = javaBooleanOr(inString, lineOffset > lineRange.offset && lineOffset < lineRange.end);
+ if (lineOffset > lineRange.end) {
+ break;
+ }
+ }
+ lineOffset += line.length + eol.length;
+ // update line indent
+ if (!inString) {
+ line = "${newIndent}${StringUtils.removeStart(line, oldIndent)}";
+ }
+ // append line
+ sb.append(line);
+ sb.append(eol);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return [InsertDesc], description where to insert new library-related directive.
+ */
+ CorrectionUtils_InsertDesc get insertDescImport {
+ // analyze directives
+ Directive prevDirective = null;
+ for (Directive directive in unit.directives) {
+ if (directive is LibraryDirective || directive is ImportDirective || directive is ExportDirective) {
+ prevDirective = directive;
+ }
+ }
+ // insert after last library-related directive
+ if (prevDirective != null) {
+ CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc();
+ result.offset = prevDirective.end;
+ String eol = endOfLine;
+ if (prevDirective is LibraryDirective) {
+ result.prefix = "${eol}${eol}";
+ } else {
+ result.prefix = eol;
+ }
+ return result;
+ }
+ // no directives, use "top" location
+ return insertDescTop;
+ }
+
+ /**
+ * @return [InsertDesc], description where to insert new 'part 'directive.
+ */
+ CorrectionUtils_InsertDesc get insertDescPart {
+ // analyze directives
+ Directive prevDirective = null;
+ for (Directive directive in unit.directives) {
+ prevDirective = directive;
+ }
+ // insert after last directive
+ if (prevDirective != null) {
+ CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc();
+ result.offset = prevDirective.end;
+ String eol = endOfLine;
+ if (prevDirective is PartDirective) {
+ result.prefix = eol;
+ } else {
+ result.prefix = "${eol}${eol}";
+ }
+ return result;
+ }
+ // no directives, use "top" location
+ return insertDescTop;
+ }
+
+ /**
+ * @return [InsertDesc], description where to insert new directive or top-level declaration
+ * at the top of file.
+ */
+ CorrectionUtils_InsertDesc get insertDescTop {
+ // skip leading line comments
+ int offset = 0;
+ bool insertEmptyLineBefore = false;
+ bool insertEmptyLineAfter = false;
+ String source = text;
+ // skip hash-bang
+ if (offset < source.length - 2) {
+ String linePrefix = getText2(offset, 2);
+ if (linePrefix == "#!") {
+ insertEmptyLineBefore = true;
+ offset = getLineNext(offset);
+ // skip empty lines to first line comment
+ int emptyOffset = offset;
+ while (emptyOffset < source.length - 2) {
+ int nextLineOffset = getLineNext(emptyOffset);
+ String line = source.substring(emptyOffset, nextLineOffset);
+ if (line.trim().isEmpty) {
+ emptyOffset = nextLineOffset;
+ continue;
+ } else if (line.startsWith("//")) {
+ offset = emptyOffset;
+ break;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ // skip line comments
+ while (offset < source.length - 2) {
+ String linePrefix = getText2(offset, 2);
+ if (linePrefix == "//") {
+ insertEmptyLineBefore = true;
+ offset = getLineNext(offset);
+ } else {
+ break;
+ }
+ }
+ // determine if empty line is required after
+ int nextLineOffset = getLineNext(offset);
+ String insertLine = source.substring(offset, nextLineOffset);
+ if (!insertLine.trim().isEmpty) {
+ insertEmptyLineAfter = true;
+ }
+ // fill InsertDesc
+ CorrectionUtils_InsertDesc desc = new CorrectionUtils_InsertDesc();
+ desc.offset = offset;
+ if (insertEmptyLineBefore) {
+ desc.prefix = endOfLine;
+ }
+ if (insertEmptyLineAfter) {
+ desc.suffix = endOfLine;
+ }
+ return desc;
+ }
+
+ /**
+ * Skips whitespace characters and single EOL on the right from the given position. If from
+ * statement or method end, then this is in the most cases start of the next line.
+ */
+ int getLineContentEnd(int index) {
+ int length = _buffer.length;
+ // skip whitespace characters
+ while (index < length) {
+ int c = _buffer.codeUnitAt(index);
+ if (!Character.isWhitespace(c) || c == 0xD || c == 0xA) {
+ break;
+ }
+ index++;
+ }
+ // skip single \r
+ if (index < length && _buffer.codeUnitAt(index) == 0xD) {
+ index++;
+ }
+ // skip single \n
+ if (index < length && _buffer.codeUnitAt(index) == 0xA) {
+ index++;
+ }
+ // done
+ return index;
+ }
+
+ /**
+ * @return the index of the last space or tab on the left from the given one, if from statement or
+ * method start, then this is in most cases start of the line.
+ */
+ int getLineContentStart(int index) {
+ while (index > 0) {
+ int c = _buffer.codeUnitAt(index - 1);
+ if (c != 0x20 && c != 0x9) {
+ break;
+ }
+ index--;
+ }
+ return index;
+ }
+
+ /**
+ * @return the start index of the next line after the line which contains given index.
+ */
+ int getLineNext(int index) {
+ int length = _buffer.length;
+ // skip whitespace characters
+ while (index < length) {
+ int c = _buffer.codeUnitAt(index);
+ if (c == 0xD || c == 0xA) {
+ break;
+ }
+ index++;
+ }
+ // skip single \r
+ if (index < length && _buffer.codeUnitAt(index) == 0xD) {
+ index++;
+ }
+ // skip single \n
+ if (index < length && _buffer.codeUnitAt(index) == 0xA) {
+ index++;
+ }
+ // done
+ return index;
+ }
+
+ /**
+ * @return the whitespace prefix of the line which contains given offset.
+ */
+ String getLinePrefix(int index) {
+ int lineStart = getLineThis(index);
+ int length = _buffer.length;
+ int lineNonWhitespace = lineStart;
+ while (lineNonWhitespace < length) {
+ int c = _buffer.codeUnitAt(lineNonWhitespace);
+ if (c == 0xD || c == 0xA) {
+ break;
+ }
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ lineNonWhitespace++;
+ }
+ return getText2(lineStart, lineNonWhitespace - lineStart);
+ }
+
+ /**
+ * @return the [getLinesRange] for given [Statement]s.
+ */
+ SourceRange getLinesRange(List<Statement> statements) {
+ SourceRange range = SourceRangeFactory.rangeNodes(statements);
+ return getLinesRange2(range);
+ }
+
+ /**
+ * @return the [SourceRange] which starts at the start of the line of "offset" and ends at
+ * the start of the next line after "end" of the given [SourceRange], i.e. basically
+ * complete lines of the source for given [SourceRange].
+ */
+ SourceRange getLinesRange2(SourceRange range) {
+ // start
+ int startOffset = range.offset;
+ int startLineOffset = getLineContentStart(startOffset);
+ // end
+ int endOffset = range.end;
+ int afterEndLineOffset = getLineContentEnd(endOffset);
+ // range
+ return SourceRangeFactory.rangeStartEnd(startLineOffset, afterEndLineOffset);
+ }
+
+ /**
+ * @return the [getLinesRange] for given [Statement]s.
+ */
+ SourceRange getLinesRange3(List<Statement> statements) => getLinesRange([]);
+
+ /**
+ * @return the start index of the line which contains given index.
+ */
+ int getLineThis(int index) {
+ while (index > 0) {
+ int c = _buffer.codeUnitAt(index - 1);
+ if (c == 0xD || c == 0xA) {
+ break;
+ }
+ index--;
+ }
+ return index;
+ }
+
+ /**
+ * @return the line prefix consisting of spaces and tabs on the left from the given
+ * [AstNode].
+ */
+ String getNodePrefix(AstNode node) {
+ int offset = node.offset;
+ // function literal is special, it uses offset of enclosing line
+ if (node is FunctionExpression) {
+ return getLinePrefix(offset);
+ }
+ // use just prefix directly before node
+ return getPrefix(offset);
+ }
+
+ /**
+ * @return the index of the first non-whitespace character after given index.
+ */
+ int getNonWhitespaceForward(int index) {
+ int length = _buffer.length;
+ // skip whitespace characters
+ while (index < length) {
+ int c = _buffer.codeUnitAt(index);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ index++;
+ }
+ // done
+ return index;
+ }
+
+ /**
+ * @return the source for the parameter with the given type and name.
+ */
+ String getParameterSource(DartType type, String name) {
+ // no type
+ if (type == null || type.isDynamic) {
+ return name;
+ }
+ // function type
+ if (type is FunctionType) {
+ FunctionType functionType = type;
+ JavaStringBuilder sb = new JavaStringBuilder();
+ // return type
+ DartType returnType = functionType.returnType;
+ if (returnType != null && !returnType.isDynamic) {
+ sb.append(getTypeSource2(returnType));
+ sb.appendChar(0x20);
+ }
+ // parameter name
+ sb.append(name);
+ // parameters
+ sb.appendChar(0x28);
+ List<ParameterElement> fParameters = functionType.parameters;
+ for (int i = 0; i < fParameters.length; i++) {
+ ParameterElement fParameter = fParameters[i];
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(getParameterSource(fParameter.type, fParameter.name));
+ }
+ sb.appendChar(0x29);
+ // done
+ return sb.toString();
+ }
+ // simple type
+ return "${getTypeSource2(type)} ${name}";
+ }
+
+ /**
+ * @return the line prefix consisting of spaces and tabs on the left from the given offset.
+ */
+ String getPrefix(int endIndex) {
+ int startIndex = getLineContentStart(endIndex);
+ return _buffer.substring(startIndex, endIndex);
+ }
+
+ /**
+ * @return the full text of unit.
+ */
+ String get text => _buffer;
+
+ /**
+ * @return the given range of text from unit.
+ */
+ String getText(AstNode node) => getText2(node.offset, node.length);
+
+ /**
+ * @return the given range of text from unit.
+ */
+ String getText2(int offset, int length) => _buffer.substring(offset, offset + length);
+
+ /**
+ * @return the given range of text from unit.
+ */
+ String getText3(SourceRange range) => getText2(range.offset, range.length);
+
+ /**
+ * @return the actual type source of the given [Expression], may be `null` if can not
+ * be resolved, should be treated as <code>Dynamic</code>.
+ */
+ String getTypeSource(Expression expression) {
+ if (expression == null) {
+ return null;
+ }
+ DartType type = expression.staticType;
+ String typeSource = getTypeSource2(type);
+ if ("dynamic" == typeSource) {
+ return null;
+ }
+ return typeSource;
+ }
+
+ /**
+ * @return the source to reference the given [Type] in this [CompilationUnit].
+ */
+ String getTypeSource2(DartType type) {
+ JavaStringBuilder sb = new JavaStringBuilder();
+ // prepare element
+ Element element = type.element;
+ if (element == null) {
+ String source = type.toString();
+ source = StringUtils.remove(source, "<dynamic>");
+ source = StringUtils.remove(source, "<dynamic, dynamic>");
+ return source;
+ }
+ // append prefix
+ {
+ ImportElement imp = _getImportElement(element);
+ if (imp != null && imp.prefix != null) {
+ sb.append(imp.prefix.displayName);
+ sb.append(".");
+ }
+ }
+ // append simple name
+ String name = element.displayName;
+ sb.append(name);
+ // may be type arguments
+ if (type is InterfaceType) {
+ InterfaceType interfaceType = type;
+ List<DartType> arguments = interfaceType.typeArguments;
+ // check if has arguments
+ bool hasArguments = false;
+ for (DartType argument in arguments) {
+ if (!argument.isDynamic) {
+ hasArguments = true;
+ break;
+ }
+ }
+ // append type arguments
+ if (hasArguments) {
+ sb.append("<");
+ for (int i = 0; i < arguments.length; i++) {
+ DartType argument = arguments[i];
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(getTypeSource2(argument));
+ }
+ sb.append(">");
+ }
+ }
+ // done
+ return sb.toString();
+ }
+
+ /**
+ * @return the source of the inverted condition for the given logical expression.
+ */
+ String invertCondition(Expression expression) => _invertCondition0(expression)._source;
+
+ /**
+ * @return <code>true</code> if selection range contains only whitespace.
+ */
+ bool isJustWhitespace(SourceRange range) => getText3(range).trim().length == 0;
+
+ /**
+ * @return <code>true</code> if selection range contains only whitespace or comments
+ */
+ bool isJustWhitespaceOrComment(SourceRange range) {
+ String trimmedText = getText3(range).trim();
+ // may be whitespace
+ if (trimmedText.isEmpty) {
+ return true;
+ }
+ // may be comment
+ return TokenUtils.getTokens(trimmedText).isEmpty;
+ }
+
+ /**
+ * @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) => _selectionIncludesNonWhitespaceOutsideRange(selection, SourceRangeFactory.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.
+ */
+ ImportElement _getImportElement(Element element) {
+ for (ImportElement imp in _library.imports) {
+ Map<String, Element> definedNames = getImportNamespace(imp);
+ if (definedNames.containsValue(element)) {
+ return imp;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the [InvertedCondition] for the given logical expression.
+ */
+ CorrectionUtils_InvertedCondition _invertCondition0(Expression expression) {
+ if (expression is BooleanLiteral) {
+ BooleanLiteral literal = expression;
+ if (literal.value) {
+ return CorrectionUtils_InvertedCondition._simple("false");
+ } else {
+ return CorrectionUtils_InvertedCondition._simple("true");
+ }
+ }
+ if (expression is BinaryExpression) {
+ BinaryExpression binary = expression;
+ TokenType operator = binary.operator.type;
+ Expression le = binary.leftOperand;
+ Expression re = binary.rightOperand;
+ CorrectionUtils_InvertedCondition ls = _invertCondition0(le);
+ CorrectionUtils_InvertedCondition rs = _invertCondition0(re);
+ if (operator == TokenType.LT) {
+ return CorrectionUtils_InvertedCondition._binary2(ls, " >= ", rs);
+ }
+ if (operator == TokenType.GT) {
+ return CorrectionUtils_InvertedCondition._binary2(ls, " <= ", rs);
+ }
+ if (operator == TokenType.LT_EQ) {
+ return CorrectionUtils_InvertedCondition._binary2(ls, " > ", rs);
+ }
+ if (operator == TokenType.GT_EQ) {
+ return CorrectionUtils_InvertedCondition._binary2(ls, " < ", rs);
+ }
+ if (operator == TokenType.EQ_EQ) {
+ return CorrectionUtils_InvertedCondition._binary2(ls, " != ", rs);
+ }
+ if (operator == TokenType.BANG_EQ) {
+ return CorrectionUtils_InvertedCondition._binary2(ls, " == ", rs);
+ }
+ if (operator == TokenType.AMPERSAND_AMPERSAND) {
+ int newPrecedence = TokenType.BAR_BAR.precedence;
+ return CorrectionUtils_InvertedCondition._binary(newPrecedence, ls, " || ", rs);
+ }
+ if (operator == TokenType.BAR_BAR) {
+ int newPrecedence = TokenType.AMPERSAND_AMPERSAND.precedence;
+ return CorrectionUtils_InvertedCondition._binary(newPrecedence, ls, " && ", rs);
+ }
+ }
+ if (expression is IsExpression) {
+ IsExpression isExpression = expression;
+ String expressionSource = getText(isExpression.expression);
+ String typeSource = getText(isExpression.type);
+ if (isExpression.notOperator == null) {
+ return CorrectionUtils_InvertedCondition._simple("${expressionSource} is! ${typeSource}");
+ } else {
+ return CorrectionUtils_InvertedCondition._simple("${expressionSource} is ${typeSource}");
+ }
+ }
+ if (expression is PrefixExpression) {
+ PrefixExpression prefixExpression = expression;
+ TokenType operator = prefixExpression.operator.type;
+ if (operator == TokenType.BANG) {
+ Expression operand = prefixExpression.operand;
+ while (operand is ParenthesizedExpression) {
+ ParenthesizedExpression pe = operand as ParenthesizedExpression;
+ operand = pe.expression;
+ }
+ return CorrectionUtils_InvertedCondition._simple(getText(operand));
+ }
+ }
+ if (expression is ParenthesizedExpression) {
+ ParenthesizedExpression pe = expression;
+ Expression innerExpresion = pe.expression;
+ while (innerExpresion is ParenthesizedExpression) {
+ innerExpresion = (innerExpresion as ParenthesizedExpression).expression;
+ }
+ return _invertCondition0(innerExpresion);
+ }
+ DartType type = expression.bestType;
+ if (type.displayName == "bool") {
+ return CorrectionUtils_InvertedCondition._simple("!${getText(expression)}");
+ }
+ return CorrectionUtils_InvertedCondition._simple(getText(expression));
+ }
+
+ bool _selectionIncludesNonWhitespaceOutsideOperands(SourceRange selection, List<Expression> operands) => _selectionIncludesNonWhitespaceOutsideRange(selection, SourceRangeFactory.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(SourceRangeFactory.rangeStartStart(selection, range))) {
+ return true;
+ }
+ // non-whitespace after range
+ if (!isJustWhitespaceOrComment(SourceRangeFactory.rangeEndEnd(range, selection))) {
+ return true;
+ }
+ // only whitespace in selection around range
+ return false;
+ }
+}
+
+/**
+ * Describes where to insert new directive or top-level declaration.
+ */
+class CorrectionUtils_InsertDesc {
+ int offset = 0;
+
+ String prefix = "";
+
+ String suffix = "";
+}
+
+/**
+ * This class is used to hold the source and also its precedence during inverting logical
+ * expressions.
+ */
+class CorrectionUtils_InvertedCondition {
+ static CorrectionUtils_InvertedCondition _binary(int precedence, CorrectionUtils_InvertedCondition left, String operation, CorrectionUtils_InvertedCondition right) => new CorrectionUtils_InvertedCondition(precedence, "${CorrectionUtils._parenthesizeIfRequired(left, precedence)}${operation}${CorrectionUtils._parenthesizeIfRequired(right, precedence)}");
+
+ static CorrectionUtils_InvertedCondition _binary2(CorrectionUtils_InvertedCondition left, String operation, CorrectionUtils_InvertedCondition right) => new CorrectionUtils_InvertedCondition(2147483647, "${left._source}${operation}${right._source}");
+
+ static CorrectionUtils_InvertedCondition _simple(String source) => new CorrectionUtils_InvertedCondition(2147483647, source);
+
+ final int _precedence;
+
+ final String _source;
+
+ CorrectionUtils_InvertedCondition(this._precedence, this._source);
+}
+
+class GeneralizingAstVisitor_CorrectionUtils_getOperandsInOrderFor extends GeneralizingAstVisitor<Object> {
+ TokenType groupOperatorType;
+
+ List<Expression> operands;
+
+ GeneralizingAstVisitor_CorrectionUtils_getOperandsInOrderFor(this.groupOperatorType, this.operands) : super();
+
+ @override
+ Object visitExpression(Expression node) {
+ if (node is BinaryExpression && node.operator.type == groupOperatorType) {
+ return super.visitNode(node);
+ }
+ operands.add(node);
+ return null;
+ }
+}
+
+class GeneralizingElementVisitor_CorrectionUtils_getChildren extends GeneralizingElementVisitor<Object> {
+ Element parent;
+
+ String name;
+
+ List<Element> children;
+
+ GeneralizingElementVisitor_CorrectionUtils_getChildren(this.parent, this.name, this.children) : super();
+
+ @override
+ Object visitElement(Element element) {
+ if (identical(element, parent)) {
+ super.visitElement(element);
+ } else if (name == null || CorrectionUtils.hasDisplayName(element, name)) {
+ children.add(element);
+ }
+ return null;
+ }
+}
+
+class GeneralizingElementVisitor_HierarchyUtils_getDirectMembers extends GeneralizingElementVisitor<Object> {
+ ClassElement clazz;
+
+ bool includeSynthetic = false;
+
+ List<Element> members;
+
+ GeneralizingElementVisitor_HierarchyUtils_getDirectMembers(this.clazz, this.includeSynthetic, this.members) : super();
+
+ @override
+ Object visitElement(Element element) {
+ if (identical(element, clazz)) {
+ return super.visitElement(element);
+ }
+ if (!includeSynthetic && element.isSynthetic) {
+ return null;
+ }
+ if (element is ConstructorElement) {
+ return null;
+ }
+ if (element is ExecutableElement) {
+ members.add(element);
+ }
+ if (element is FieldElement) {
+ members.add(element);
+ }
+ return null;
+ }
+}
+
+class NameOccurrencesFinder extends RecursiveAstVisitor<Object> {
+ static Iterable<AstNode> findIn(SimpleIdentifier ident, AstNode root) {
+ if (ident == null || ident.bestElement == null) {
+ return new Set<AstNode>();
+ }
+ NameOccurrencesFinder finder = new NameOccurrencesFinder(ident.bestElement);
+ root.accept(finder);
+ return finder.matches;
+ }
+
+ Element _target;
+
+ Element _target2;
+
+ Element _target3;
+
+ Element _target4;
+
+ Set<AstNode> _matches;
+
+ NameOccurrencesFinder(Element source) {
+ this._target = source;
+ while (true) {
+ if (source.kind == ElementKind.GETTER || source.kind == ElementKind.SETTER) {
+ PropertyAccessorElement accessorElem = source as PropertyAccessorElement;
+ this._target2 = accessorElem.variable;
+ if (source is Member) {
+ Member member = source;
+ this._target4 = member.baseElement;
+ }
+ if (this._target2 is Member) {
+ Member member = source as Member;
+ this._target3 = member.baseElement;
+ }
+ } else if (source.kind == ElementKind.FIELD || source.kind == ElementKind.TOP_LEVEL_VARIABLE) {
+ PropertyInducingElement propertyElem = source as PropertyInducingElement;
+ this._target2 = propertyElem.getter;
+ this._target3 = propertyElem.setter;
+ } else if (source.kind == ElementKind.METHOD) {
+ if (source is Member) {
+ Member member = source;
+ this._target4 = member.baseElement;
+ }
+ } else if (source.kind == ElementKind.PARAMETER) {
+ ParameterElement param = source as ParameterElement;
+ if (param.isInitializingFormal) {
+ FieldFormalParameterElement fieldInit = param as FieldFormalParameterElement;
+ this._target2 = fieldInit.field;
+ }
+ } else {
+ }
+ break;
+ }
+ if (_target2 == null) {
+ _target2 = _target;
+ }
+ if (_target3 == null) {
+ _target3 = _target;
+ }
+ if (_target4 == null) {
+ _target4 = _target;
+ }
+ this._matches = new Set<AstNode>();
+ }
+
+ Iterable<AstNode> get matches => _matches;
+
+ @override
+ Object visitSimpleIdentifier(SimpleIdentifier node) {
+ Element element = node.bestElement;
+ if (element == null) {
+ return null;
+ }
+ _match(element, node);
+ if (element is Member) {
+ Member member = element;
+ _match(member.baseElement, node);
+ }
+ while (true) {
+ if (element.kind == ElementKind.GETTER || element.kind == ElementKind.SETTER) {
+ PropertyAccessorElement accessorElem = element as PropertyAccessorElement;
+ _match(accessorElem.variable, node);
+ } else if (element.kind == ElementKind.FIELD || element.kind == ElementKind.TOP_LEVEL_VARIABLE) {
+ PropertyInducingElement propertyElem = element as PropertyInducingElement;
+ _match(propertyElem.getter, node);
+ _match(propertyElem.setter, node);
+ } else if (element.kind == ElementKind.PARAMETER) {
+ ParameterElement param = element as ParameterElement;
+ if (param.isInitializingFormal) {
+ FieldFormalParameterElement fieldInit = param as FieldFormalParameterElement;
+ _match(fieldInit.field, node);
+ }
+ } else {
+ }
+ break;
+ }
+ return null;
+ }
+
+ void _match(Element element, AstNode node) {
+ if (identical(_target, element) || identical(_target2, element) || identical(_target3, element) || identical(_target4, element)) {
+ _matches.add(node);
+ }
+ }
+}
+
+/**
+ * Abstract visitor for visiting [AstNode]s covered by the selection [SourceRange].
+ */
+class SelectionAnalyzer extends GeneralizingAstVisitor<Object> {
+ SourceRange selection;
+
+ AstNode _coveringNode;
+
+ List<AstNode> _selectedNodes;
+
+ SelectionAnalyzer(SourceRange selection) {
+ assert(selection != null);
+ this.selection = selection;
+ }
+
+ /**
+ * @return the [AstNode] with the shortest length which completely covers the specified
+ * selection.
+ */
+ AstNode get coveringNode => _coveringNode;
+
+ /**
+ * @return the first selected [AstNode], may be <code>null</code>.
+ */
+ AstNode get firstSelectedNode {
+ if (_selectedNodes == null || _selectedNodes.isEmpty) {
+ return null;
+ }
+ return _selectedNodes[0];
+ }
+
+ /**
+ * @return the last selected [AstNode], may be <code>null</code>.
+ */
+ AstNode get lastSelectedNode {
+ if (_selectedNodes == null || _selectedNodes.isEmpty) {
+ return null;
+ }
+ return _selectedNodes[_selectedNodes.length - 1];
+ }
+
+ /**
+ * @return the [SourceRange] which covers selected [AstNode]s, may be
+ * <code>null</code> if no [AstNode]s under selection.
+ */
+ SourceRange get selectedNodeRange {
+ if (_selectedNodes == null || _selectedNodes.isEmpty) {
+ return null;
+ }
+ AstNode firstNode = _selectedNodes[0];
+ AstNode lastNode = _selectedNodes[_selectedNodes.length - 1];
+ return SourceRangeFactory.rangeStartEnd(firstNode, lastNode);
+ }
+
+ /**
+ * @return the [AstNode]s fully covered by the selection [SourceRange].
+ */
+ List<AstNode> get selectedNodes {
+ if (_selectedNodes == null || _selectedNodes.isEmpty) {
+ return [];
+ }
+ return _selectedNodes;
+ }
+
+ /**
+ * @return <code>true</code> if there are [AstNode] fully covered by the selection
+ * [SourceRange].
+ */
+ bool get hasSelectedNodes => _selectedNodes != null && !_selectedNodes.isEmpty;
+
+ @override
+ Object visitNode(AstNode node) {
+ SourceRange nodeRange = SourceRangeFactory.rangeNode(node);
+ if (selection.covers(nodeRange)) {
+ if (isFirstNode) {
+ handleFirstSelectedNode(node);
+ } else {
+ handleNextSelectedNode(node);
+ }
+ return null;
+ } else if (selection.coveredBy(nodeRange)) {
+ _coveringNode = node;
+ node.visitChildren(this);
+ return null;
+ } else if (selection.startsIn(nodeRange)) {
+ handleSelectionStartsIn(node);
+ node.visitChildren(this);
+ return null;
+ } else if (selection.endsIn(nodeRange)) {
+ handleSelectionEndsIn(node);
+ node.visitChildren(this);
+ return null;
+ }
+ // no intersection
+ return null;
+ }
+
+ /**
+ * Adds first selected [AstNode].
+ */
+ void handleFirstSelectedNode(AstNode node) {
+ _selectedNodes = [];
+ _selectedNodes.add(node);
+ }
+
+ /**
+ * Adds second or more selected [AstNode].
+ */
+ void handleNextSelectedNode(AstNode node) {
+ if (identical(firstSelectedNode.parent, node.parent)) {
+ _selectedNodes.add(node);
+ }
+ }
+
+ /**
+ * Notifies that selection ends in given [AstNode].
+ */
+ void handleSelectionEndsIn(AstNode node) {
+ }
+
+ /**
+ * Notifies that selection starts in given [AstNode].
+ */
+ void handleSelectionStartsIn(AstNode node) {
+ }
+
+ /**
+ * Resets selected nodes.
+ */
+ void reset() {
+ _selectedNodes = null;
+ }
+
+ /**
+ * @return <code>true</code> if there was no selected nodes yet.
+ */
+ bool get isFirstNode => _selectedNodes == null;
+}
+
+/**
+ * Helper for building Dart source with tracked positions.
+ */
+class SourceBuilder {
+ final int offset;
+
+ JavaStringBuilder _buffer = new JavaStringBuilder();
+
+ Map<String, List<SourceRange>> _linkedPositions = {};
+
+ final Map<String, List<LinkedPositionProposal>> linkedProposals = {};
+
+ String _currentPositionGroupId;
+
+ int _currentPositionStart = 0;
+
+ int _endPosition = -1;
+
+ SourceBuilder.con1(this.offset);
+
+ SourceBuilder.con2(SourceRange offsetRange) : this.con1(offsetRange.offset);
+
+ /**
+ * Adds proposal for the current position, may be called after [startPosition].
+ */
+ void addProposal(CorrectionImage icon, String text) {
+ List<LinkedPositionProposal> proposals = linkedProposals[_currentPositionGroupId];
+ if (proposals == null) {
+ proposals = [];
+ linkedProposals[_currentPositionGroupId] = proposals;
+ }
+ proposals.add(new LinkedPositionProposal(icon, text));
+ }
+
+ /**
+ * Appends source to the buffer.
+ */
+ SourceBuilder append(String s) {
+ _buffer.append(s);
+ return this;
+ }
+
+ /**
+ * Ends position started using [startPosition].
+ */
+ void endPosition() {
+ assert(_currentPositionGroupId != null);
+ _addPosition();
+ _currentPositionGroupId = null;
+ }
+
+ /**
+ * @return the "end position" for the [CorrectionProposal], may be <code>-1</code> if not
+ * set in this [SourceBuilder].
+ */
+ int get endPosition2 {
+ if (_endPosition == -1) {
+ return -1;
+ }
+ return offset + _endPosition;
+ }
+
+ /**
+ * @return the [Map] or position IDs to their locations.
+ */
+ Map<String, List<SourceRange>> get linkedPositions => _linkedPositions;
+
+ /**
+ * @return the length of the built source.
+ */
+ int length() => _buffer.length;
+
+ /**
+ * Marks current position as "end position" of the [CorrectionProposal].
+ */
+ void setEndPosition() {
+ _endPosition = _buffer.length;
+ }
+
+ /**
+ * Sets text-only proposals for the current position.
+ */
+ void set proposals(List<String> proposals) {
+ List<LinkedPositionProposal> proposalList = [];
+ for (String proposalText in proposals) {
+ proposalList.add(new LinkedPositionProposal(null, proposalText));
+ }
+ linkedProposals[_currentPositionGroupId] = proposalList;
+ }
+
+ /**
+ * Starts linked position with given ID.
+ */
+ void startPosition(String groupId) {
+ assert(_currentPositionGroupId == null);
+ _currentPositionGroupId = groupId;
+ _currentPositionStart = _buffer.length;
+ }
+
+ @override
+ String toString() => _buffer.toString();
+
+ /**
+ * Adds position location [SourceRange] using current fields.
+ */
+ void _addPosition() {
+ List<SourceRange> locations = _linkedPositions[_currentPositionGroupId];
+ if (locations == null) {
+ locations = [];
+ _linkedPositions[_currentPositionGroupId] = locations;
+ }
+ int start = offset + _currentPositionStart;
+ int end = offset + _buffer.length;
+ locations.add(SourceRangeFactory.rangeStartEnd(start, end));
+ }
+}
+
+/**
+ * Analyzer to check if a selection covers a valid set of statements of AST.
+ */
+class StatementAnalyzer extends SelectionAnalyzer {
+ /**
+ * @return <code>true</code> if "nodes" contains "node".
+ */
+ static bool _contains(List<AstNode> nodes, AstNode node) => nodes.contains(node);
+
+ /**
+ * @return <code>true</code> if "nodes" contains one of the "otherNodes".
+ */
+ static bool _contains2(List<AstNode> nodes, List<AstNode> otherNodes) {
+ for (AstNode otherNode in otherNodes) {
+ if (nodes.contains(otherNode)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ CorrectionUtils utils;
+
+ RefactoringStatus _status = new RefactoringStatus();
+
+ StatementAnalyzer.con1(CompilationUnit cunit, SourceRange selection) : this.con2(new CorrectionUtils(cunit), selection);
+
+ StatementAnalyzer.con2(CorrectionUtils utils, SourceRange selection) : super(selection) {
+ this.utils = utils;
+ }
+
+ /**
+ * @return the [RefactoringStatus] result of checking selection.
+ */
+ RefactoringStatus get status => _status;
+
+ @override
+ Object visitCompilationUnit(CompilationUnit node) {
+ super.visitCompilationUnit(node);
+ if (!hasSelectedNodes) {
+ return null;
+ }
+ // check that selection does not begin/end in comment
+ {
+ int selectionStart = selection.offset;
+ int selectionEnd = selection.end;
+ List<SourceRange> commentRanges = utils.commentRanges;
+ for (SourceRange commentRange in commentRanges) {
+ if (commentRange.contains(selectionStart)) {
+ invalidSelection("Selection begins inside a comment.");
+ }
+ if (commentRange.containsExclusive(selectionEnd)) {
+ invalidSelection("Selection ends inside a comment.");
+ }
+ }
+ }
+ // more checks
+ if (!_status.hasFatalError) {
+ _checkSelectedNodes(node);
+ }
+ return null;
+ }
+
+ @override
+ Object visitDoStatement(DoStatement node) {
+ super.visitDoStatement(node);
+ List<AstNode> selectedNodes = this.selectedNodes;
+ if (_contains(selectedNodes, node.body)) {
+ invalidSelection("Operation not applicable to a 'do' statement's body and expression.");
+ }
+ return null;
+ }
+
+ @override
+ Object visitForStatement(ForStatement node) {
+ super.visitForStatement(node);
+ List<AstNode> selectedNodes = this.selectedNodes;
+ bool containsInit = _contains(selectedNodes, node.initialization) || _contains(selectedNodes, node.variables);
+ bool containsCondition = _contains(selectedNodes, node.condition);
+ bool containsUpdaters = _contains2(selectedNodes, node.updaters);
+ bool containsBody = _contains(selectedNodes, node.body);
+ if (containsInit && containsCondition) {
+ invalidSelection("Operation not applicable to a 'for' statement's initializer and condition.");
+ } else if (containsCondition && containsUpdaters) {
+ invalidSelection("Operation not applicable to a 'for' statement's condition and updaters.");
+ } else if (containsUpdaters && containsBody) {
+ invalidSelection("Operation not applicable to a 'for' statement's updaters and body.");
+ }
+ return null;
+ }
+
+ @override
+ Object visitSwitchStatement(SwitchStatement node) {
+ super.visitSwitchStatement(node);
+ List<AstNode> selectedNodes = this.selectedNodes;
+ List<SwitchMember> switchMembers = node.members;
+ for (AstNode selectedNode in selectedNodes) {
+ if (switchMembers.contains(selectedNode)) {
+ invalidSelection("Selection must either cover whole switch statement or parts of a single case block.");
+ break;
+ }
+ }
+ return null;
+ }
+
+ @override
+ Object visitTryStatement(TryStatement node) {
+ super.visitTryStatement(node);
+ AstNode firstSelectedNode = this.firstSelectedNode;
+ if (firstSelectedNode != null) {
+ if (identical(firstSelectedNode, node.body) || identical(firstSelectedNode, node.finallyBlock)) {
+ invalidSelection("Selection must either cover whole try statement or parts of try, catch, or finally block.");
+ } else {
+ List<CatchClause> catchClauses = node.catchClauses;
+ for (CatchClause catchClause in catchClauses) {
+ if (identical(firstSelectedNode, catchClause) || identical(firstSelectedNode, catchClause.body) || identical(firstSelectedNode, catchClause.exceptionParameter)) {
+ invalidSelection("Selection must either cover whole try statement or parts of try, catch, or finally block.");
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @override
+ Object visitWhileStatement(WhileStatement node) {
+ super.visitWhileStatement(node);
+ List<AstNode> selectedNodes = this.selectedNodes;
+ if (_contains(selectedNodes, node.condition) && _contains(selectedNodes, node.body)) {
+ invalidSelection("Operation not applicable to a while statement's expression and body.");
+ }
+ return null;
+ }
+
+ /**
+ * Records fatal error with given message.
+ */
+ void invalidSelection(String message) {
+ invalidSelection2(message, null);
+ }
+
+ /**
+ * Records fatal error with given message and [RefactoringStatusContext].
+ */
+ void invalidSelection2(String message, RefactoringStatusContext context) {
+ _status.addFatalError(message, context);
+ reset();
+ }
+
+ /**
+ * Checks final selected [AstNode]s after processing [CompilationUnit].
+ */
+ void _checkSelectedNodes(CompilationUnit unit) {
+ List<AstNode> nodes = selectedNodes;
+ // some tokens before first selected node
+ {
+ AstNode firstNode = nodes[0];
+ SourceRange rangeBeforeFirstNode = SourceRangeFactory.rangeStartStart(selection, firstNode);
+ if (_hasTokens(rangeBeforeFirstNode)) {
+ invalidSelection2("The beginning of the selection contains characters that do not belong to a statement.", new RefactoringStatusContext.forUnit(unit, rangeBeforeFirstNode));
+ }
+ }
+ // some tokens after last selected node
+ {
+ AstNode lastNode = nodes.last;
+ SourceRange rangeAfterLastNode = SourceRangeFactory.rangeEndEnd(lastNode, selection);
+ if (_hasTokens(rangeAfterLastNode)) {
+ invalidSelection2("The end of the selection contains characters that do not belong to a statement.", new RefactoringStatusContext.forUnit(unit, rangeAfterLastNode));
+ }
+ }
+ }
+
+ /**
+ * @return the [Token]s in given [SourceRange].
+ */
+ List<Token> _getTokens(SourceRange range) {
+ try {
+ String text = utils.getText3(range);
+ return TokenUtils.getTokens(text);
+ } catch (e) {
+ return [];
+ }
+ }
+
+ /**
+ * @return <code>true</code> if there are [Token]s in the given [SourceRange].
+ */
+ bool _hasTokens(SourceRange range) => !_getTokens(range).isEmpty;
+}
+
+/**
+ * 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;
+}
+
+class URIUtils {
+ /**
+ * Computes relative relative path to reference "target" from "base". Uses ".." if needed, in
+ * contrast to [URI#relativize].
+ */
+ static String computeRelativePath(String base, String target) {
+ // convert to URI separator
+ base = base.replaceAll("\\\\", "/");
+ target = target.replaceAll("\\\\", "/");
+ if (base.startsWith("/") && target.startsWith("/")) {
+ base = base.substring(1);
+ target = target.substring(1);
+ }
+ // equal paths - no relative
+ if (base == target) {
+ return null;
+ }
+ // split paths
+ List<String> baseParts = base.split("/");
+ List<String> targetParts = target.split("/");
+ // prepare maximum possible common root length
+ int length = baseParts.length < targetParts.length ? baseParts.length : targetParts.length;
+ // find common root
+ int lastCommonRoot = -1;
+ for (int i = 0; i < length; i++) {
+ if (baseParts[i] == targetParts[i]) {
+ lastCommonRoot = i;
+ } else {
+ break;
+ }
+ }
+ // append ..
+ JavaStringBuilder relativePath = new JavaStringBuilder();
+ for (int i = lastCommonRoot + 1; i < baseParts.length; i++) {
+ if (baseParts[i].length > 0) {
+ relativePath.append("../");
+ }
+ }
+ // append target folder names
+ for (int i = lastCommonRoot + 1; i < targetParts.length - 1; i++) {
+ String p = targetParts[i];
+ relativePath.append(p);
+ relativePath.append("/");
+ }
+ // append target file name
+ relativePath.append(targetParts[targetParts.length - 1]);
+ // done
+ return relativePath.toString();
+ }
+}
« no previous file with comments | « pkg/analysis_services/lib/src/generated/stubs.dart ('k') | pkg/analysis_services/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698