blob: 8daaf7bf65223ddb955b8df3f7c466ba25983119 [file] [log] [blame]
// 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.
import 'dart:async';
import 'dart:collection';
import 'dart:math';
import 'package:analysis_server/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/plugin/edit/assist/assist_dart.dart';
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/base_processor.dart';
import 'package:analysis_server/src/services/correction/name_suggestion.dart';
import 'package:analysis_server/src/services/correction/selection_analyzer.dart';
import 'package:analysis_server/src/services/correction/statement_analyzer.dart';
import 'package:analysis_server/src/services/correction/util.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analysis_server/src/services/search/hierarchy.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/precedence.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart'
hide AssistContributor;
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' hide context;
typedef _SimpleIdentifierVisitor(SimpleIdentifier node);
/**
* The computer for Dart assists.
*/
class AssistProcessor extends BaseProcessor {
final DartAssistContext context;
final List<Assist> assists = <Assist>[];
AssistProcessor(this.context)
: super(
selectionOffset: context.selectionOffset,
selectionLength: context.selectionLength,
resolvedResult: context.resolveResult,
workspace: context.workspace,
);
Future<List<Assist>> compute() async {
if (!setupCompute()) {
return assists;
}
if (!_containsErrorCode(
{LintNames.always_specify_types, LintNames.type_annotate_public_apis},
)) {
await _addProposals_addTypeAnnotation();
}
await _addProposal_addNotNullAssert();
await _addProposal_assignToLocalVariable();
await _addProposal_convertClassToMixin();
await _addProposal_convertDocumentationIntoBlock();
if (!_containsErrorCode(
{LintNames.slash_for_doc_comments},
)) {
await _addProposal_convertDocumentationIntoLine();
}
await _addProposal_convertIntoFinalField();
await _addProposal_convertIntoGetter();
await _addProposal_convertListConstructorToListLiteral();
await _addProposal_convertListToSetToSetLiteral();
await _addProposal_convertMapConstructorToMapLiteral();
await _addProposal_convertPartOfToUri();
await _addProposal_convertSetConstructorToSetLiteral();
await _addProposal_convertToAsyncFunctionBody();
await _addProposal_convertToBlockFunctionBody();
await _addProposal_convertToDoubleQuotedString();
if (!_containsErrorCode(
{LintNames.prefer_expression_function_bodies},
)) {
await _addProposal_convertToExpressionFunctionBody();
}
await _addProposal_convertToFieldParameter();
await _addProposal_convertToForIndexLoop();
await _addProposal_convertToGenericFunctionSyntax();
if (!_containsErrorCode(
{LintNames.prefer_int_literals},
)) {
await _addProposal_convertToIntLiteral();
}
await _addProposal_convertToIsNot_onIs();
await _addProposal_convertToIsNot_onNot();
await _addProposal_convertToIsNotEmpty();
await _addProposal_convertToMultilineString();
await _addProposal_convertToNormalParameter();
if (!_containsErrorCode(
{LintNames.prefer_null_aware_operators},
)) {
await _addProposal_convertToNullAware();
}
if (!_containsErrorCode(
{LintNames.avoid_relative_lib_imports},
)) {
await _addProposal_convertToPackageImport();
}
if (!_containsErrorCode(
{LintNames.prefer_single_quotes},
)) {
await _addProposal_convertToSingleQuotedString();
}
await _addProposal_encapsulateField();
await _addProposal_exchangeOperands();
await _addProposal_flutterConvertToChildren();
await _addProposal_flutterConvertToStatefulWidget();
await _addProposal_flutterMoveWidgetDown();
await _addProposal_flutterMoveWidgetUp();
await _addProposal_flutterRemoveWidget_singleChild();
await _addProposal_flutterRemoveWidget_multipleChildren();
await _addProposal_flutterSwapWithChild();
await _addProposal_flutterSwapWithParent();
await _addProposal_flutterWrapStreamBuilder();
await _addProposal_flutterWrapWidget();
await _addProposal_flutterWrapWidgets();
await _addProposal_importAddShow();
if (!_containsErrorCode(
{LintNames.prefer_inlined_adds},
)) {
await _addProposal_inlineAdd();
}
await _addProposal_introduceLocalTestedType();
await _addProposal_invertIf();
await _addProposal_joinIfStatementInner();
await _addProposal_joinIfStatementOuter();
await _addProposal_joinVariableDeclaration_onAssignment();
await _addProposal_joinVariableDeclaration_onDeclaration();
await _addProposal_removeTypeAnnotation();
await _addProposal_reparentFlutterList();
await _addProposal_replaceConditionalWithIfElse();
await _addProposal_replaceIfElseWithConditional();
if (!_containsErrorCode(
{LintNames.sort_child_properties_last},
)) {
await _addProposal_sortChildPropertyLast();
}
await _addProposal_splitAndCondition();
await _addProposal_splitVariableDeclaration();
await _addProposal_surroundWith();
if (!_containsErrorCode(
{LintNames.curly_braces_in_flow_control_structures},
)) {
await _addProposal_useCurlyBraces();
}
if (experimentStatus.control_flow_collections) {
if (!_containsErrorCode(
{LintNames.prefer_if_elements_to_conditional_expressions},
)) {
await _addProposal_convertConditionalExpressionToIfElement();
}
if (!_containsErrorCode(
{LintNames.prefer_for_elements_to_map_fromIterable},
)) {
await _addProposal_convertMapFromIterableToForLiteral();
}
}
if (experimentStatus.spread_collections) {
final preferSpreadsLintFound =
_containsErrorCode({LintNames.prefer_spread_collections});
final preferInlinedAddsLintFound =
_containsErrorCode({LintNames.prefer_inlined_adds});
if (!_containsErrorCode(
{LintNames.prefer_spread_collections},
)) {
await _addProposal_convertAddAllToSpread(
preferInlinedAdds: !preferInlinedAddsLintFound,
convertToSpreads: !preferSpreadsLintFound);
}
}
return assists;
}
Future<List<Assist>> computeAssist(AssistKind assistKind) async {
if (!setupCompute()) {
return assists;
}
// Calculate only specific assists for edit.dartFix
if (assistKind == DartAssistKind.CONVERT_CLASS_TO_MIXIN) {
await _addProposal_convertClassToMixin();
} else if (assistKind == DartAssistKind.CONVERT_TO_INT_LITERAL) {
await _addProposal_convertToIntLiteral();
} else if (assistKind == DartAssistKind.CONVERT_TO_SPREAD) {
if (experimentStatus.spread_collections) {
await _addProposal_convertAddAllToSpread();
}
} else if (assistKind == DartAssistKind.CONVERT_TO_FOR_ELEMENT) {
if (experimentStatus.control_flow_collections) {
await _addProposal_convertMapFromIterableToForLiteral();
}
} else if (assistKind == DartAssistKind.CONVERT_TO_IF_ELEMENT) {
if (experimentStatus.control_flow_collections) {
await _addProposal_convertConditionalExpressionToIfElement();
}
}
return assists;
}
void _addAssistFromBuilder(DartChangeBuilder builder, AssistKind kind,
{List args = null}) {
if (builder == null) {
return;
}
SourceChange change = builder.sourceChange;
if (change.edits.isEmpty) {
_coverageMarker();
return;
}
change.id = kind.id;
change.message = formatList(kind.message, args);
assists.add(new Assist(kind, change));
}
Future<void> _addProposal_addNotNullAssert() async {
final identifier = this.node;
if (identifier is SimpleIdentifier) {
if (identifier.parent is FormalParameter) {
final exp = identifier.parent.thisOrAncestorMatching(
(node) => node is FunctionExpression || node is MethodDeclaration);
var body;
if (exp is FunctionExpression) {
body = exp.body;
} else if (exp is MethodDeclaration) {
body = exp.body;
}
if (body is BlockFunctionBody) {
// Check for an obvious pre-existing assertion.
for (var statement in body.block.statements) {
if (statement is AssertStatement) {
final condition = statement.condition;
if (condition is BinaryExpression) {
final leftOperand = condition.leftOperand;
if (leftOperand is SimpleIdentifier) {
if (leftOperand.staticElement == identifier.staticElement &&
condition.operator.type == TokenType.BANG_EQ &&
condition.rightOperand is NullLiteral) {
return;
}
}
}
}
}
final changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
final id = identifier.name;
final prefix = utils.getNodePrefix(exp);
final indent = utils.getIndent(1);
// todo (pq): follow-ups:
// 1. if the end token is on the same line as the body
// we should add an `eol` before the assert as well.
// 2. also, consider asking the block for the list of statements and
// adding the statement to the beginning of the list, special casing
// when there are no statements (or when there's a single statement
// and the whole block is on the same line).
final int offset = min(utils.getLineNext(body.beginToken.offset),
body.endToken.offset);
builder.addSimpleInsertion(
offset, '$prefix${indent}assert($id != null);$eol');
_addAssistFromBuilder(
changeBuilder, DartAssistKind.ADD_NOT_NULL_ASSERT);
});
}
}
}
}
Future<void> _addProposal_assignToLocalVariable() async {
// prepare enclosing ExpressionStatement
ExpressionStatement expressionStatement;
for (AstNode node = this.node; node != null; node = node.parent) {
if (node is ExpressionStatement) {
expressionStatement = node;
break;
}
if (node is ArgumentList ||
node is AssignmentExpression ||
node is Statement ||
node is ThrowExpression) {
_coverageMarker();
return;
}
}
if (expressionStatement == null) {
_coverageMarker();
return;
}
// prepare expression
Expression expression = expressionStatement.expression;
int offset = expression.offset;
// prepare expression type
DartType type = expression.staticType;
if (type.isVoid) {
_coverageMarker();
return;
}
// prepare excluded names
Set<String> excluded = new Set<String>();
ScopedNameFinder scopedNameFinder = new ScopedNameFinder(offset);
expression.accept(scopedNameFinder);
excluded.addAll(scopedNameFinder.locals.keys.toSet());
List<String> suggestions =
getVariableNameSuggestionsForExpression(type, expression, excluded);
if (suggestions.isNotEmpty) {
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addInsertion(offset, (DartEditBuilder builder) {
builder.write('var ');
builder.addSimpleLinkedEdit('NAME', suggestions[0],
kind: LinkedEditSuggestionKind.VARIABLE,
suggestions: suggestions);
builder.write(' = ');
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.ASSIGN_TO_LOCAL_VARIABLE);
}
}
Future<void> _addProposal_convertAddAllToSpread(
{bool preferInlinedAdds = true, bool convertToSpreads = true}) async {
final change = await createBuilder_convertAddAllToSpread();
if (change != null) {
if (change.isLineInvocation && !preferInlinedAdds || !convertToSpreads) {
return;
}
final kind = change.isLineInvocation
? DartAssistKind.INLINE_INVOCATION
: DartAssistKind.CONVERT_TO_SPREAD;
_addAssistFromBuilder(change.builder, kind, args: change.args);
}
}
Future<void> _addProposal_convertClassToMixin() async {
ClassDeclaration classDeclaration =
node.thisOrAncestorOfType<ClassDeclaration>();
if (classDeclaration == null) {
return;
}
if (selectionOffset > classDeclaration.name.end ||
selectionEnd < classDeclaration.classKeyword.offset) {
return;
}
if (classDeclaration.members
.any((member) => member is ConstructorDeclaration)) {
return;
}
_SuperclassReferenceFinder finder = new _SuperclassReferenceFinder();
classDeclaration.accept(finder);
List<ClassElement> referencedClasses = finder.referencedClasses;
List<InterfaceType> superclassConstraints = <InterfaceType>[];
List<InterfaceType> interfaces = <InterfaceType>[];
ClassElement classElement = classDeclaration.declaredElement;
for (InterfaceType type in classElement.mixins) {
if (referencedClasses.contains(type.element)) {
superclassConstraints.add(type);
} else {
interfaces.add(type);
}
}
ExtendsClause extendsClause = classDeclaration.extendsClause;
if (extendsClause != null) {
if (referencedClasses.length > superclassConstraints.length) {
superclassConstraints.insert(0, classElement.supertype);
} else {
interfaces.insert(0, classElement.supertype);
}
}
interfaces.addAll(classElement.interfaces);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(
range.startStart(
classDeclaration.abstractKeyword ?? classDeclaration.classKeyword,
classDeclaration.leftBracket), (DartEditBuilder builder) {
builder.write('mixin ');
builder.write(classDeclaration.name.name);
builder.writeTypeParameters(
classDeclaration.declaredElement.typeParameters);
builder.writeTypes(superclassConstraints, prefix: ' on ');
builder.writeTypes(interfaces, prefix: ' implements ');
builder.write(' ');
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_CLASS_TO_MIXIN);
}
Future<void> _addProposal_convertConditionalExpressionToIfElement() async {
final changeBuilder =
await createBuilder_convertConditionalExpressionToIfElement();
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_TO_IF_ELEMENT);
}
Future<void> _addProposal_convertDocumentationIntoBlock() async {
Comment comment = node.thisOrAncestorOfType<Comment>();
if (comment == null || !comment.isDocumentation) {
return;
}
var tokens = comment.tokens;
if (tokens.isEmpty ||
tokens.any((Token token) =>
token is! DocumentationCommentToken ||
token.type != TokenType.SINGLE_LINE_COMMENT)) {
return;
}
String prefix = utils.getNodePrefix(comment);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(comment), (DartEditBuilder builder) {
builder.writeln('/**');
for (Token token in comment.tokens) {
builder.write(prefix);
builder.write(' *');
builder.writeln(token.lexeme.substring('///'.length));
}
builder.write(prefix);
builder.write(' */');
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_DOCUMENTATION_INTO_BLOCK);
}
Future<void> _addProposal_convertDocumentationIntoLine() async {
final changeBuilder = await createBuilder_convertDocumentationIntoLine();
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_DOCUMENTATION_INTO_LINE);
}
Future<void> _addProposal_convertIntoFinalField() async {
// Find the enclosing getter.
MethodDeclaration getter;
for (AstNode n = node; n != null; n = n.parent) {
if (n is MethodDeclaration) {
getter = n;
break;
}
if (n is SimpleIdentifier ||
n is TypeAnnotation ||
n is TypeArgumentList) {
continue;
}
break;
}
if (getter == null || !getter.isGetter) {
return;
}
// Check that there is no corresponding setter.
{
ExecutableElement element = getter.declaredElement;
if (element == null) {
return;
}
Element enclosing = element.enclosingElement;
if (enclosing is ClassElement) {
if (enclosing.getSetter(element.name) != null) {
return;
}
}
}
// Try to find the returned expression.
Expression expression;
{
FunctionBody body = getter.body;
if (body is ExpressionFunctionBody) {
expression = body.expression;
} else if (body is BlockFunctionBody) {
List<Statement> statements = body.block.statements;
if (statements.length == 1) {
Statement statement = statements.first;
if (statement is ReturnStatement) {
expression = statement.expression;
}
}
}
}
// Use the returned expression as the field initializer.
if (expression != null) {
String code = 'final';
if (getter.returnType != null) {
code += ' ' + _getNodeText(getter.returnType);
}
code += ' ' + _getNodeText(getter.name);
if (expression is! NullLiteral) {
code += ' = ' + _getNodeText(expression);
}
code += ';';
SourceRange replacementRange =
range.startEnd(getter.returnType ?? getter.propertyKeyword, getter);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(replacementRange, code);
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_INTO_FINAL_FIELD);
}
}
Future<void> _addProposal_convertIntoGetter() async {
// Find the enclosing field declaration.
FieldDeclaration fieldDeclaration;
for (AstNode n = node; n != null; n = n.parent) {
if (n is FieldDeclaration) {
fieldDeclaration = n;
break;
}
if (n is SimpleIdentifier ||
n is VariableDeclaration ||
n is VariableDeclarationList ||
n is TypeAnnotation ||
n is TypeArgumentList) {
continue;
}
break;
}
if (fieldDeclaration == null) {
return;
}
// The field must be final and has only one variable.
VariableDeclarationList fieldList = fieldDeclaration.fields;
if (!fieldList.isFinal || fieldList.variables.length != 1) {
return;
}
VariableDeclaration field = fieldList.variables.first;
// Prepare the initializer.
Expression initializer = field.initializer;
if (initializer == null) {
return;
}
// Add proposal.
String code = '';
if (fieldList.type != null) {
code += _getNodeText(fieldList.type) + ' ';
}
code += 'get';
code += ' ' + _getNodeText(field.name);
code += ' => ' + _getNodeText(initializer);
code += ';';
SourceRange replacementRange =
range.startEnd(fieldList.keyword, fieldDeclaration);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(replacementRange, code);
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_INTO_GETTER);
}
Future<void> _addProposal_convertListConstructorToListLiteral() async {
//
// Ensure that this is the default constructor defined on `List`.
//
InstanceCreationExpression creation = node.thisOrAncestorOfType();
if (creation == null ||
node.offset > creation.argumentList.offset ||
creation.staticType.element != typeProvider.listElement ||
creation.constructorName.name != null ||
creation.argumentList.arguments.isNotEmpty) {
_coverageMarker();
return;
}
//
// Extract the information needed to build the edit.
//
TypeArgumentList constructorTypeArguments =
creation.constructorName.type.typeArguments;
//
// Build the change and return the assist.
//
DartChangeBuilder changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(creation), (DartEditBuilder builder) {
if (constructorTypeArguments != null) {
builder.write(_getNodeText(constructorTypeArguments));
}
builder.write('[]');
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_TO_LIST_LITERAL);
}
Future<void> _addProposal_convertListToSetToSetLiteral() async {
//
// Ensure that this is an invocation of `toSet` on a list literal.
//
if (node is! SimpleIdentifier) {
_coverageMarker();
return;
}
AstNode parent = node.parent;
if (parent is! MethodInvocation) {
_coverageMarker();
return;
}
MethodInvocation invocation = parent as MethodInvocation;
if (invocation.methodName != node ||
invocation.methodName.name != 'toSet') {
_coverageMarker();
return;
}
//
// Extract the information needed to build the edit.
//
Expression target = invocation.target;
bool hasTypeArgs;
SourceRange openRange;
SourceRange closeRange;
if (target is ListLiteral) {
hasTypeArgs = target.typeArguments != null;
openRange = range.token(target.leftBracket);
closeRange = range.startEnd(target.rightBracket, invocation);
} else {
_coverageMarker();
return;
}
//
// Build the change and return the assist.
//
DartChangeBuilder changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
if (hasTypeArgs || _listHasUnambiguousElement(target)) {
builder.addSimpleReplacement(openRange, '{');
} else {
builder.addSimpleReplacement(openRange, '<dynamic>{');
}
builder.addSimpleReplacement(closeRange, '}');
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_TO_SET_LITERAL);
}
Future<void> _addProposal_convertMapConstructorToMapLiteral() async {
bool isMapClass(Element element) =>
element is ClassElement &&
(element == typeProvider.mapElement ||
(element.name == 'LinkedHashMap' &&
element.library.name == 'dart.collection'));
//
// Ensure that this is the default constructor defined on either `Map` or
// `LinkedHashMap`.
//
InstanceCreationExpression creation = node.thisOrAncestorOfType();
if (creation == null ||
node.offset > creation.argumentList.offset ||
creation.constructorName.name != null ||
creation.argumentList.arguments.isNotEmpty ||
!isMapClass(creation.staticType.element)) {
_coverageMarker();
return;
}
//
// Extract the information needed to build the edit.
//
TypeArgumentList constructorTypeArguments =
creation.constructorName.type.typeArguments;
//
// Build the change and return the assist.
//
DartChangeBuilder changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(creation), (DartEditBuilder builder) {
if (constructorTypeArguments != null) {
builder.write(_getNodeText(constructorTypeArguments));
}
builder.write('{}');
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_TO_MAP_LITERAL);
}
Future<void> _addProposal_convertMapFromIterableToForLiteral() async {
final changeBuilder =
await createBuilder_convertMapFromIterableToForLiteral();
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_TO_FOR_ELEMENT);
}
Future<void> _addProposal_convertPartOfToUri() async {
PartOfDirective directive = node.thisOrAncestorOfType<PartOfDirective>();
if (directive == null || directive.libraryName == null) {
return;
}
String libraryPath = context.resolveResult.libraryElement.source.fullName;
String partPath = context.resolveResult.path;
String relativePath = relative(libraryPath, from: dirname(partPath));
String uri = new Uri.file(relativePath).toString();
SourceRange replacementRange = range.node(directive.libraryName);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(replacementRange, "'$uri'");
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_PART_OF_TO_URI);
}
Future<void> _addProposal_convertSetConstructorToSetLiteral() async {
//
// Ensure that this is one of the constructors defined on `Set`.
//
InstanceCreationExpression creation = node.thisOrAncestorOfType();
if (creation == null ||
node.offset > creation.argumentList.offset ||
creation.staticType.element != typeProvider.setElement) {
// TODO(brianwilkerson) Consider also accepting uses of LinkedHashSet.
_coverageMarker();
return;
}
//
// Extract the information needed to build the edit.
//
SimpleIdentifier name = creation.constructorName.name;
TypeArgumentList constructorTypeArguments =
creation.constructorName.type.typeArguments;
TypeArgumentList elementTypeArguments;
SourceRange elementsRange;
if (name == null) {
// Handle an invocation of the default constructor `Set()`.
} else if (name.name == 'from' || name.name == 'of') {
// Handle an invocation of the constructor `Set.from()` or `Set.of()`.
NodeList<Expression> arguments = creation.argumentList.arguments;
if (arguments.length != 1) {
_coverageMarker();
return;
}
if (arguments[0] is ListLiteral) {
ListLiteral elements = arguments[0] as ListLiteral;
elementTypeArguments = elements.typeArguments;
elementsRange =
range.endStart(elements.leftBracket, elements.rightBracket);
} else {
// TODO(brianwilkerson) Consider handling other iterables. Literal sets
// could be treated like lists, and arbitrary iterables by using a
// spread.
_coverageMarker();
return;
}
} else {
// Invocation of an unhandled constructor.
_coverageMarker();
return;
}
new Map();
//
// Determine whether type arguments are strictly required in cases where no
// type arguments were explicitly provided.
//
bool hasUnambiguousElement() {
NodeList<Expression> arguments = creation.argumentList.arguments;
if (arguments == null || arguments.isEmpty) {
return false;
}
return _listHasUnambiguousElement(arguments[0]);
}
bool setWouldBeInferred() {
AstNode parent = creation.parent;
if (parent is VariableDeclaration) {
AstNode parent2 = parent.parent;
if (parent2 is VariableDeclarationList &&
parent2.type?.type?.element == typeProvider.setElement) {
return true;
}
}
return hasUnambiguousElement();
}
//
// Build the change and return the assist.
//
DartChangeBuilder changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(creation), (DartEditBuilder builder) {
if (constructorTypeArguments != null) {
builder.write(_getNodeText(constructorTypeArguments));
} else if (elementTypeArguments != null) {
builder.write(_getNodeText(elementTypeArguments));
} else if (!setWouldBeInferred()) {
builder.write('<dynamic>');
}
builder.write('{');
if (elementsRange != null) {
builder.write(_getRangeText(elementsRange));
}
builder.write('}');
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_TO_SET_LITERAL);
}
Future<void> _addProposal_convertToAsyncFunctionBody() async {
FunctionBody body = getEnclosingFunctionBody();
if (body == null ||
body is EmptyFunctionBody ||
body.isAsynchronous ||
body.isGenerator) {
_coverageMarker();
return;
}
// Function bodies can be quite large, e.g. Flutter build() methods.
// It is surprising to see this Quick Assist deep in a function body.
if (body is BlockFunctionBody &&
selectionOffset > body.block.beginToken.end) {
return;
}
if (body is ExpressionFunctionBody &&
selectionOffset > body.beginToken.end) {
return;
}
AstNode parent = body.parent;
if (parent is ConstructorDeclaration) {
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.convertFunctionFromSyncToAsync(body, typeProvider);
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_INTO_ASYNC_BODY);
}
Future<void> _addProposal_convertToBlockFunctionBody() async {
FunctionBody body = getEnclosingFunctionBody();
// prepare expression body
if (body is! ExpressionFunctionBody || body.isGenerator) {
_coverageMarker();
return;
}
Expression returnValue = (body as ExpressionFunctionBody).expression;
// Return expressions can be quite large, e.g. Flutter build() methods.
// It is surprising to see this Quick Assist deep in the function body.
if (selectionOffset >= returnValue.offset) {
_coverageMarker();
return;
}
DartType returnValueType = returnValue.staticType;
String returnValueCode = _getNodeText(returnValue);
// prepare prefix
String prefix = utils.getNodePrefix(body.parent);
String indent = utils.getIndent(1);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(body), (DartEditBuilder builder) {
if (body.isAsynchronous) {
builder.write('async ');
}
builder.write('{$eol$prefix$indent');
if (!returnValueType.isVoid && !returnValueType.isBottom) {
builder.write('return ');
}
builder.write(returnValueCode);
builder.write(';');
builder.selectHere();
builder.write('$eol$prefix}');
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_INTO_BLOCK_BODY);
}
Future<void> _addProposal_convertToDoubleQuotedString() async {
await _convertQuotes(false, DartAssistKind.CONVERT_TO_DOUBLE_QUOTED_STRING);
}
Future<void> _addProposal_convertToExpressionFunctionBody() async {
final changeBuilder = await createBuilder_convertToExpressionFunctionBody();
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_INTO_EXPRESSION_BODY);
}
Future<void> _addProposal_convertToFieldParameter() async {
if (node == null) {
return;
}
// prepare ConstructorDeclaration
ConstructorDeclaration constructor =
node.thisOrAncestorOfType<ConstructorDeclaration>();
if (constructor == null) {
return;
}
FormalParameterList parameterList = constructor.parameters;
List<ConstructorInitializer> initializers = constructor.initializers;
// prepare parameter
SimpleFormalParameter parameter;
if (node.parent is SimpleFormalParameter &&
node.parent.parent is FormalParameterList &&
node.parent.parent.parent is ConstructorDeclaration) {
parameter = node.parent;
}
if (node is SimpleIdentifier &&
node.parent is ConstructorFieldInitializer) {
String name = (node as SimpleIdentifier).name;
ConstructorFieldInitializer initializer = node.parent;
if (initializer.expression == node) {
for (FormalParameter formalParameter in parameterList.parameters) {
if (formalParameter is SimpleFormalParameter &&
formalParameter.identifier.name == name) {
parameter = formalParameter;
}
}
}
}
// analyze parameter
if (parameter != null) {
String parameterName = parameter.identifier.name;
ParameterElement parameterElement = parameter.declaredElement;
// check number of references
{
int numOfReferences = 0;
AstVisitor visitor =
new _SimpleIdentifierRecursiveAstVisitor((SimpleIdentifier node) {
if (node.staticElement == parameterElement) {
numOfReferences++;
}
});
for (ConstructorInitializer initializer in initializers) {
initializer.accept(visitor);
}
if (numOfReferences != 1) {
return;
}
}
// find the field initializer
ConstructorFieldInitializer parameterInitializer;
for (ConstructorInitializer initializer in initializers) {
if (initializer is ConstructorFieldInitializer) {
Expression expression = initializer.expression;
if (expression is SimpleIdentifier &&
expression.name == parameterName) {
parameterInitializer = initializer;
}
}
}
if (parameterInitializer == null) {
return;
}
String fieldName = parameterInitializer.fieldName.name;
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
// replace parameter
builder.addSimpleReplacement(range.node(parameter), 'this.$fieldName');
// remove initializer
int initializerIndex = initializers.indexOf(parameterInitializer);
if (initializers.length == 1) {
builder
.addDeletion(range.endEnd(parameterList, parameterInitializer));
} else {
if (initializerIndex == 0) {
ConstructorInitializer next = initializers[initializerIndex + 1];
builder.addDeletion(range.startStart(parameterInitializer, next));
} else {
ConstructorInitializer prev = initializers[initializerIndex - 1];
builder.addDeletion(range.endEnd(prev, parameterInitializer));
}
}
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_TO_FIELD_PARAMETER);
}
}
Future<void> _addProposal_convertToForIndexLoop() async {
// find enclosing ForEachStatement
ForStatement forEachStatement = node.thisOrAncestorMatching(
(node) => node is ForStatement && node.forLoopParts is ForEachParts);
if (forEachStatement == null) {
_coverageMarker();
return;
}
ForEachParts forEachParts = forEachStatement.forLoopParts;
if (selectionOffset < forEachStatement.offset ||
forEachStatement.rightParenthesis.end < selectionOffset) {
_coverageMarker();
return;
}
// loop should declare variable
DeclaredIdentifier loopVariable =
forEachParts is ForEachPartsWithDeclaration
? forEachParts.loopVariable
: null;
if (loopVariable == null) {
_coverageMarker();
return;
}
// iterable should be VariableElement
String listName;
Expression iterable = forEachParts.iterable;
if (iterable is SimpleIdentifier &&
iterable.staticElement is VariableElement) {
listName = iterable.name;
} else {
_coverageMarker();
return;
}
// iterable should be List
{
DartType iterableType = iterable.staticType;
if (iterableType is! InterfaceType ||
iterableType.element != typeProvider.listElement) {
_coverageMarker();
return;
}
}
// body should be Block
if (forEachStatement.body is! Block) {
_coverageMarker();
return;
}
Block body = forEachStatement.body;
// prepare a name for the index variable
String indexName;
{
Set<String> conflicts =
utils.findPossibleLocalVariableConflicts(forEachStatement.offset);
if (!conflicts.contains('i')) {
indexName = 'i';
} else if (!conflicts.contains('j')) {
indexName = 'j';
} else if (!conflicts.contains('k')) {
indexName = 'k';
} else {
_coverageMarker();
return;
}
}
// prepare environment
String prefix = utils.getNodePrefix(forEachStatement);
String indent = utils.getIndent(1);
int firstBlockLine = utils.getLineContentEnd(body.leftBracket.end);
// add change
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
// TODO(brianwilkerson) Create linked positions for the loop variable.
builder.addSimpleReplacement(
range.startEnd(forEachStatement, forEachStatement.rightParenthesis),
'for (int $indexName = 0; $indexName < $listName.length; $indexName++)');
builder.addSimpleInsertion(firstBlockLine,
'$prefix$indent$loopVariable = $listName[$indexName];$eol');
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_INTO_FOR_INDEX);
}
Future<void> _addProposal_convertToGenericFunctionSyntax() async {
AstNode node = this.node;
while (node != null) {
if (node is FunctionTypeAlias) {
await _convertFunctionTypeAliasToGenericTypeAlias(node);
return;
} else if (node is FunctionTypedFormalParameter) {
await _convertFunctionTypedFormalParameterToSimpleFormalParameter(node);
return;
} else if (node is FormalParameterList) {
// It would be confusing for this assist to alter a surrounding context
// when the selection is inside a parameter list.
return;
}
node = node.parent;
}
}
Future<void> _addProposal_convertToIntLiteral() async {
final changeBuilder = await createBuilder_convertToIntLiteral();
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_TO_INT_LITERAL);
}
Future<void> _addProposal_convertToIsNot_onIs() async {
// may be child of "is"
AstNode node = this.node;
while (node != null && node is! IsExpression) {
node = node.parent;
}
// prepare "is"
if (node is! IsExpression) {
_coverageMarker();
return;
}
IsExpression isExpression = node as IsExpression;
if (isExpression.notOperator != null) {
_coverageMarker();
return;
}
// prepare enclosing ()
AstNode parent = isExpression.parent;
if (parent is! ParenthesizedExpression) {
_coverageMarker();
return;
}
ParenthesizedExpression parExpression = parent as ParenthesizedExpression;
// prepare enclosing !()
AstNode parent2 = parent.parent;
if (parent2 is! PrefixExpression) {
_coverageMarker();
return;
}
PrefixExpression prefExpression = parent2 as PrefixExpression;
if (prefExpression.operator.type != TokenType.BANG) {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
if (getExpressionParentPrecedence(prefExpression) >=
Precedence.relational) {
builder.addDeletion(range.token(prefExpression.operator));
} else {
builder.addDeletion(
range.startEnd(prefExpression, parExpression.leftParenthesis));
builder.addDeletion(
range.startEnd(parExpression.rightParenthesis, prefExpression));
}
builder.addSimpleInsertion(isExpression.isOperator.end, '!');
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_INTO_IS_NOT);
}
Future<void> _addProposal_convertToIsNot_onNot() async {
// may be () in prefix expression
AstNode node = this.node;
if (node is ParenthesizedExpression && node.parent is PrefixExpression) {
node = node.parent;
}
// prepare !()
if (node is! PrefixExpression) {
_coverageMarker();
return;
}
PrefixExpression prefExpression = node as PrefixExpression;
// should be ! operator
if (prefExpression.operator.type != TokenType.BANG) {
_coverageMarker();
return;
}
// prepare !()
Expression operand = prefExpression.operand;
if (operand is! ParenthesizedExpression) {
_coverageMarker();
return;
}
ParenthesizedExpression parExpression = operand as ParenthesizedExpression;
operand = parExpression.expression;
// prepare "is"
if (operand is! IsExpression) {
_coverageMarker();
return;
}
IsExpression isExpression = operand as IsExpression;
if (isExpression.notOperator != null) {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
if (getExpressionParentPrecedence(prefExpression) >=
Precedence.relational) {
builder.addDeletion(range.token(prefExpression.operator));
} else {
builder.addDeletion(
range.startEnd(prefExpression, parExpression.leftParenthesis));
builder.addDeletion(
range.startEnd(parExpression.rightParenthesis, prefExpression));
}
builder.addSimpleInsertion(isExpression.isOperator.end, '!');
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_INTO_IS_NOT);
}
/**
* Converts "!isEmpty" -> "isNotEmpty" if possible.
*/
Future<void> _addProposal_convertToIsNotEmpty() async {
// prepare "expr.isEmpty"
AstNode isEmptyAccess = null;
SimpleIdentifier isEmptyIdentifier = null;
if (node is SimpleIdentifier) {
SimpleIdentifier identifier = node as SimpleIdentifier;
AstNode parent = identifier.parent;
// normal case (but rare)
if (parent is PropertyAccess) {
isEmptyIdentifier = parent.propertyName;
isEmptyAccess = parent;
}
// usual case
if (parent is PrefixedIdentifier) {
isEmptyIdentifier = parent.identifier;
isEmptyAccess = parent;
}
}
if (isEmptyIdentifier == null) {
_coverageMarker();
return;
}
// should be "isEmpty"
Element propertyElement = isEmptyIdentifier.staticElement;
if (propertyElement == null || 'isEmpty' != propertyElement.name) {
_coverageMarker();
return;
}
// should have "isNotEmpty"
Element propertyTarget = propertyElement.enclosingElement;
if (propertyTarget == null ||
getChildren(propertyTarget, 'isNotEmpty').isEmpty) {
_coverageMarker();
return;
}
// should be in PrefixExpression
if (isEmptyAccess.parent is! PrefixExpression) {
_coverageMarker();
return;
}
PrefixExpression prefixExpression =
isEmptyAccess.parent as PrefixExpression;
// should be !
if (prefixExpression.operator.type != TokenType.BANG) {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addDeletion(
range.startStart(prefixExpression, prefixExpression.operand));
builder.addSimpleReplacement(range.node(isEmptyIdentifier), 'isNotEmpty');
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_INTO_IS_NOT_EMPTY);
}
Future<void> _addProposal_convertToMultilineString() async {
var node = this.node;
if (node is InterpolationElement) {
node = (node as InterpolationElement).parent;
}
if (node is SingleStringLiteral) {
SingleStringLiteral literal = node;
if (!literal.isMultiline) {
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
var newQuote = literal.isSingleQuoted ? "'''" : '"""';
builder.addReplacement(
SourceRange(literal.offset + (literal.isRaw ? 1 : 0), 1),
(builder) {
builder.writeln(newQuote);
},
);
builder.addSimpleReplacement(
SourceRange(literal.end - 1, 1),
newQuote,
);
});
_addAssistFromBuilder(
changeBuilder,
DartAssistKind.CONVERT_TO_MULTILINE_STRING,
);
}
}
}
Future<void> _addProposal_convertToNormalParameter() async {
if (node is SimpleIdentifier &&
node.parent is FieldFormalParameter &&
node.parent.parent is FormalParameterList &&
node.parent.parent.parent is ConstructorDeclaration) {
ConstructorDeclaration constructor = node.parent.parent.parent;
FormalParameterList parameterList = node.parent.parent;
FieldFormalParameter parameter = node.parent;
ParameterElement parameterElement = parameter.declaredElement;
String name = (node as SimpleIdentifier).name;
// prepare type
DartType type = parameterElement.type;
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
// replace parameter
if (type.isDynamic) {
builder.addSimpleReplacement(range.node(parameter), name);
} else {
builder.addReplacement(range.node(parameter),
(DartEditBuilder builder) {
builder.writeType(type);
builder.write(' ');
builder.write(name);
});
}
// add field initializer
List<ConstructorInitializer> initializers = constructor.initializers;
if (initializers.isEmpty) {
builder.addSimpleInsertion(parameterList.end, ' : $name = $name');
} else {
builder.addSimpleInsertion(initializers.last.end, ', $name = $name');
}
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_TO_NORMAL_PARAMETER);
}
}
Future<void> _addProposal_convertToNullAware() async {
final changeBuilder = await createBuilder_convertToNullAware();
_addAssistFromBuilder(changeBuilder, DartAssistKind.CONVERT_TO_NULL_AWARE);
}
Future<void> _addProposal_convertToPackageImport() async {
final changeBuilder = await createBuilder_convertToPackageImport();
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_TO_PACKAGE_IMPORT);
}
Future<void> _addProposal_convertToSingleQuotedString() async {
await _convertQuotes(true, DartAssistKind.CONVERT_TO_SINGLE_QUOTED_STRING);
}
Future<void> _addProposal_encapsulateField() async {
// find FieldDeclaration
FieldDeclaration fieldDeclaration =
node.thisOrAncestorOfType<FieldDeclaration>();
if (fieldDeclaration == null) {
_coverageMarker();
return;
}
// not interesting for static
if (fieldDeclaration.isStatic) {
_coverageMarker();
return;
}
// has a parse error
VariableDeclarationList variableList = fieldDeclaration.fields;
if (variableList.keyword == null && variableList.type == null) {
_coverageMarker();
return;
}
// not interesting for final
if (variableList.isFinal) {
_coverageMarker();
return;
}
// should have exactly one field
List<VariableDeclaration> fields = variableList.variables;
if (fields.length != 1) {
_coverageMarker();
return;
}
VariableDeclaration field = fields.first;
SimpleIdentifier nameNode = field.name;
FieldElement fieldElement = nameNode.staticElement;
// should have a public name
String name = nameNode.name;
if (Identifier.isPrivateName(name)) {
_coverageMarker();
return;
}
// should be on the name
if (nameNode != node) {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
// rename field
builder.addSimpleReplacement(range.node(nameNode), '_$name');
// update references in constructors
ClassOrMixinDeclaration classDeclaration = fieldDeclaration.parent;
for (ClassMember member in classDeclaration.members) {
if (member is ConstructorDeclaration) {
for (FormalParameter parameter in member.parameters.parameters) {
ParameterElement parameterElement = parameter.declaredElement;
if (parameterElement is FieldFormalParameterElement &&
parameterElement.field == fieldElement) {
SimpleIdentifier identifier = parameter.identifier;
builder.addSimpleReplacement(range.node(identifier), '_$name');
}
}
}
}
// Write getter and setter.
builder.addInsertion(fieldDeclaration.end, (builder) {
String docCode;
if (fieldDeclaration.documentationComment != null) {
docCode = utils.getNodeText(fieldDeclaration.documentationComment);
}
String typeCode = '';
if (variableList.type != null) {
typeCode = _getNodeText(variableList.type) + ' ';
}
// Write getter.
builder.writeln();
builder.writeln();
if (docCode != null) {
builder.write(' ');
builder.writeln(docCode);
}
builder.write(' ${typeCode}get $name => _$name;');
// Write setter.
builder.writeln();
builder.writeln();
if (docCode != null) {
builder.write(' ');
builder.writeln(docCode);
}
builder.writeln(' set $name($typeCode$name) {');
builder.writeln(' _$name = $name;');
builder.write(' }');
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.ENCAPSULATE_FIELD);
}
Future<void> _addProposal_exchangeOperands() async {
// check that user invokes quick assist on binary expression
if (node is! BinaryExpression) {
_coverageMarker();
return;
}
BinaryExpression binaryExpression = node as BinaryExpression;
// prepare operator position
if (!_isOperatorSelected(
binaryExpression, selectionOffset, selectionLength)) {
_coverageMarker();
return;
}
// add edits
Expression leftOperand = binaryExpression.leftOperand;
Expression rightOperand = binaryExpression.rightOperand;
// find "wide" enclosing binary expression with same operator
while (binaryExpression.parent is BinaryExpression) {
BinaryExpression newBinaryExpression =
binaryExpression.parent as BinaryExpression;
if (newBinaryExpression.operator.type != binaryExpression.operator.type) {
_coverageMarker();
break;
}
binaryExpression = newBinaryExpression;
}
// exchange parts of "wide" expression parts
SourceRange leftRange = range.startEnd(binaryExpression, leftOperand);
SourceRange rightRange = range.startEnd(rightOperand, binaryExpression);
// maybe replace the operator
Token operator = binaryExpression.operator;
// prepare a new operator
String newOperator = null;
TokenType operatorType = operator.type;
if (operatorType == TokenType.LT) {
newOperator = '>';
} else if (operatorType == TokenType.LT_EQ) {
newOperator = '>=';
} else if (operatorType == TokenType.GT) {
newOperator = '<';
} else if (operatorType == TokenType.GT_EQ) {
newOperator = '<=';
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(leftRange, _getRangeText(rightRange));
builder.addSimpleReplacement(rightRange, _getRangeText(leftRange));
// Optionally replace the operator.
if (newOperator != null) {
builder.addSimpleReplacement(range.token(operator), newOperator);
}
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.EXCHANGE_OPERANDS);
}
Future<void> _addProposal_flutterConvertToChildren() async {
// Find "child: widget" under selection.
NamedExpression namedExp;
{
AstNode node = this.node;
AstNode parent = node?.parent;
AstNode parent2 = parent?.parent;
if (node is SimpleIdentifier &&
parent is Label &&
parent2 is NamedExpression &&
node.name == 'child' &&
node.staticElement != null &&
flutter.isWidgetExpression(parent2.expression)) {
namedExp = parent2;
} else {
_coverageMarker();
return;
}
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
_convertFlutterChildToChildren(namedExp, eol, utils.getNodeText,
utils.getLinePrefix, utils.getIndent, utils.getText, builder);
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.FLUTTER_CONVERT_TO_CHILDREN);
}
Future<void> _addProposal_flutterConvertToStatefulWidget() async {
ClassDeclaration widgetClass =
node.thisOrAncestorOfType<ClassDeclaration>();
TypeName superclass = widgetClass?.extendsClause?.superclass;
if (widgetClass == null || superclass == null) {
_coverageMarker();
return;
}
// Don't spam, activate only from the `class` keyword to the class body.
if (selectionOffset < widgetClass.classKeyword.offset ||
selectionOffset > widgetClass.leftBracket.end) {
_coverageMarker();
return;
}
// Find the build() method.
MethodDeclaration buildMethod;
for (var member in widgetClass.members) {
if (member is MethodDeclaration &&
member.name.name == 'build' &&
member.parameters != null &&
member.parameters.parameters.length == 1) {
buildMethod = member;
break;
}
}
if (buildMethod == null) {
_coverageMarker();
return;
}
// Must be a StatelessWidget subclasses.
ClassElement widgetClassElement = widgetClass.declaredElement;
if (!flutter.isExactlyStatelessWidgetType(widgetClassElement.supertype)) {
_coverageMarker();
return;
}
String widgetName = widgetClassElement.displayName;
String stateName = '_${widgetName}State';
// Find fields assigned in constructors.
var fieldsAssignedInConstructors = new Set<FieldElement>();
for (var member in widgetClass.members) {
if (member is ConstructorDeclaration) {
member.accept(new _SimpleIdentifierRecursiveAstVisitor((node) {
if (node.parent is FieldFormalParameter) {
Element element = node.staticElement;
if (element is FieldFormalParameterElement) {
fieldsAssignedInConstructors.add(element.field);
}
}
if (node.parent is ConstructorFieldInitializer) {
Element element = node.staticElement;
if (element is FieldElement) {
fieldsAssignedInConstructors.add(element);
}
}
if (node.inSetterContext()) {
Element element = node.staticElement;
if (element is PropertyAccessorElement) {
PropertyInducingElement field = element.variable;
if (field is FieldElement) {
fieldsAssignedInConstructors.add(field);
}
}
}
}));
}
}
// Prepare nodes to move.
var nodesToMove = new Set<ClassMember>();
var elementsToMove = new Set<Element>();
for (var member in widgetClass.members) {
if (member is FieldDeclaration && !member.isStatic) {
for (VariableDeclaration fieldNode in member.fields.variables) {
FieldElement fieldElement = fieldNode.declaredElement;
if (!fieldsAssignedInConstructors.contains(fieldElement)) {
nodesToMove.add(member);
elementsToMove.add(fieldElement);
elementsToMove.add(fieldElement.getter);
if (fieldElement.setter != null) {
elementsToMove.add(fieldElement.setter);
}
}
}
}
if (member is MethodDeclaration && !member.isStatic) {
nodesToMove.add(member);
elementsToMove.add(member.declaredElement);
}
}
/// Return the code for the [movedNode] which is suitable to be used
/// inside the `State` class, so that references to the widget fields and
/// methods, that are not moved, are qualified with the corresponding
/// instance `widget.`, or static `MyWidgetClass.` qualifier.
String rewriteWidgetMemberReferences(AstNode movedNode) {
var linesRange = utils.getLinesRange(range.node(movedNode));
var text = utils.getRangeText(linesRange);
// Insert `widget.` before references to the widget instance members.
final List<SourceEdit> edits = [];
movedNode.accept(new _SimpleIdentifierRecursiveAstVisitor((node) {
if (node.inDeclarationContext()) {
return;
}
var element = node.staticElement;
if (element is ExecutableElement &&
element?.enclosingElement == widgetClassElement &&
!elementsToMove.contains(element)) {
var offset = node.offset - linesRange.offset;
var qualifier = element.isStatic ? widgetName : 'widget';
AstNode parent = node.parent;
if (parent is InterpolationExpression &&
parent.leftBracket.type ==
TokenType.STRING_INTERPOLATION_IDENTIFIER) {
edits.add(new SourceEdit(offset, 0, '{$qualifier.'));
edits.add(new SourceEdit(offset + node.length, 0, '}'));
} else {
edits.add(new SourceEdit(offset, 0, '$qualifier.'));
}
}
}));
return SourceEdit.applySequence(text, edits.reversed);
}
var statefulWidgetClass = await sessionHelper.getClass(
flutter.widgetsUri,
'StatefulWidget',
);
var stateClass = await sessionHelper.getClass(
flutter.widgetsUri,
'State',
);
if (statefulWidgetClass == null || stateClass == null) {
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(superclass), (builder) {
builder.writeReference(statefulWidgetClass);
});
int replaceOffset = 0;
bool hasBuildMethod = false;
/// Replace code between [replaceOffset] and [replaceEnd] with
/// `createState()`, empty line, or nothing.
void replaceInterval(int replaceEnd,
{bool replaceWithEmptyLine = false,
bool hasEmptyLineBeforeCreateState = false,
bool hasEmptyLineAfterCreateState = true}) {
int replaceLength = replaceEnd - replaceOffset;
builder.addReplacement(
new SourceRange(replaceOffset, replaceLength),
(builder) {
if (hasBuildMethod) {
if (hasEmptyLineBeforeCreateState) {
builder.writeln();
}
builder.writeln(' @override');
builder.writeln(' $stateName createState() => $stateName();');
if (hasEmptyLineAfterCreateState) {
builder.writeln();
}
hasBuildMethod = false;
} else if (replaceWithEmptyLine) {
builder.writeln();
}
},
);
replaceOffset = 0;
}
// Remove continuous ranges of lines of nodes being moved.
bool lastToRemoveIsField = false;
int endOfLastNodeToKeep = 0;
for (var node in widgetClass.members) {
if (nodesToMove.contains(node)) {
if (replaceOffset == 0) {
var linesRange = utils.getLinesRange(range.node(node));
replaceOffset = linesRange.offset;
}
if (node == buildMethod) {
hasBuildMethod = true;
}
lastToRemoveIsField = node is FieldDeclaration;
} else {
var linesRange = utils.getLinesRange(range.node(node));
endOfLastNodeToKeep = linesRange.end;
if (replaceOffset != 0) {
replaceInterval(linesRange.offset,
replaceWithEmptyLine:
lastToRemoveIsField && node is! FieldDeclaration);
}
}
}
// Remove nodes at the end of the widget class.
if (replaceOffset != 0) {
// Remove from the last node to keep, so remove empty lines.
if (endOfLastNodeToKeep != 0) {
replaceOffset = endOfLastNodeToKeep;
}
replaceInterval(widgetClass.rightBracket.offset,
hasEmptyLineBeforeCreateState: endOfLastNodeToKeep != 0,
hasEmptyLineAfterCreateState: false);
}
// Create the State subclass.
builder.addInsertion(widgetClass.end, (builder) {
var stateType = stateClass.instantiate(
typeArguments: [
widgetClassElement.instantiate(
typeArguments: const [],
nullabilitySuffix: NullabilitySuffix.none,
),
],
nullabilitySuffix: NullabilitySuffix.none,
);
builder.writeln();
builder.writeln();
builder.writeClassDeclaration(stateName, superclass: stateType,
membersWriter: () {
bool writeEmptyLine = false;
for (var member in nodesToMove) {
if (writeEmptyLine) {
builder.writeln();
}
String text = rewriteWidgetMemberReferences(member);
builder.write(text);
// Write empty lines between members, but not before the first.
writeEmptyLine = true;
}
});
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.FLUTTER_CONVERT_TO_STATEFUL_WIDGET);
}
Future<void> _addProposal_flutterMoveWidgetDown() async {
var widget = flutter.identifyWidgetExpression(node);
if (widget == null) {
return;
}
AstNode parentList = widget.parent;
if (parentList is ListLiteral) {
List<CollectionElement> parentElements = parentList.elements;
int index = parentElements.indexOf(widget);
if (index != parentElements.length - 1) {
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
CollectionElement nextWidget = parentElements[index + 1];
var nextRange = range.node(nextWidget);
var nextText = utils.getRangeText(nextRange);
var widgetRange = range.node(widget);
var widgetText = utils.getRangeText(widgetRange);
builder.addSimpleReplacement(nextRange, widgetText);
builder.addSimpleReplacement(widgetRange, nextText);
int lengthDelta = nextRange.length - widgetRange.length;
int newWidgetOffset = nextRange.offset + lengthDelta;
changeBuilder.setSelection(new Position(file, newWidgetOffset));
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.FLUTTER_MOVE_DOWN);
}
}
}
Future<void> _addProposal_flutterMoveWidgetUp() async {
var widget = flutter.identifyWidgetExpression(node);
if (widget == null) {
return;
}
AstNode parentList = widget.parent;
if (parentList is ListLiteral) {
List<CollectionElement> parentElements = parentList.elements;
int index = parentElements.indexOf(widget);
if (index > 0) {
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
CollectionElement previousWidget = parentElements[index - 1];
var previousRange = range.node(previousWidget);
var previousText = utils.getRangeText(previousRange);
var widgetRange = range.node(widget);
var widgetText = utils.getRangeText(widgetRange);
builder.addSimpleReplacement(previousRange, widgetText);
builder.addSimpleReplacement(widgetRange, previousText);
int newWidgetOffset = previousRange.offset;
changeBuilder.setSelection(new Position(file, newWidgetOffset));
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.FLUTTER_MOVE_UP);
}
}
}
Future<void> _addProposal_flutterRemoveWidget_multipleChildren() async {
var widgetCreation = flutter.identifyNewExpression(node);
if (widgetCreation == null) {
return;
}
// Prepare the list of our children.
List<CollectionElement> childrenExpressions;
{
var childrenArgument = flutter.findChildrenArgument(widgetCreation);
var childrenExpression = childrenArgument?.expression;
if (childrenExpression is ListLiteral &&
childrenExpression.elements.isNotEmpty) {
childrenExpressions = childrenExpression.elements;
} else {
return;
}
}
// We can inline the list of our children only into another list.
var widgetParentNode = widgetCreation.parent;
if (childrenExpressions.length > 1 && widgetParentNode is! ListLiteral) {
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
var firstChild = childrenExpressions.first;
var lastChild = childrenExpressions.last;
var childText = utils.getRangeText(range.startEnd(firstChild, lastChild));
var indentOld = utils.getLinePrefix(firstChild.offset);
var indentNew = utils.getLinePrefix(widgetCreation.offset);
childText = _replaceSourceIndent(childText, indentOld, indentNew);
builder.addSimpleReplacement(range.node(widgetCreation), childText);
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.FLUTTER_REMOVE_WIDGET);
}
Future<void> _addProposal_flutterRemoveWidget_singleChild() async {
var widgetCreation = flutter.identifyNewExpression(node);
if (widgetCreation == null) {
return;
}
var childArgument = flutter.findChildArgument(widgetCreation);
if (childArgument == null) {
return;
}
// child: ThisWidget(child: ourChild)
// children: [foo, ThisWidget(child: ourChild), bar]
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
var childExpression = childArgument.expression;
var childText = utils.getNodeText(childExpression);
var indentOld = utils.getLinePrefix(childExpression.offset);
var indentNew = utils.getLinePrefix(widgetCreation.offset);
childText = _replaceSourceIndent(childText, indentOld, indentNew);
builder.addSimpleReplacement(range.node(widgetCreation), childText);
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.FLUTTER_REMOVE_WIDGET);
}
Future<void> _addProposal_flutterSwapWithChild() async {
InstanceCreationExpression parent = flutter.identifyNewExpression(node);
if (!flutter.isWidgetCreation(parent)) {
_coverageMarker();
return;
}
NamedExpression childArgument = flutter.findChildArgument(parent);
if (childArgument?.expression is! InstanceCreationExpression ||
!flutter.isWidgetCreation(childArgument.expression)) {
_coverageMarker();
return;
}
InstanceCreationExpression child = childArgument.expression;
await _swapParentAndChild(
parent, child, DartAssistKind.FLUTTER_SWAP_WITH_CHILD);
}
Future<void> _addProposal_flutterSwapWithParent() async {
InstanceCreationExpression child = flutter.identifyNewExpression(node);
if (!flutter.isWidgetCreation(child)) {
_coverageMarker();
return;
}
// NamedExpression (child:), ArgumentList, InstanceCreationExpression
AstNode expr = child.parent?.parent?.parent;
if (expr is! InstanceCreationExpression) {
_coverageMarker();
return;
}
InstanceCreationExpression parent = expr;
await _swapParentAndChild(
parent, child, DartAssistKind.FLUTTER_SWAP_WITH_PARENT);
}
Future<void> _addProposal_flutterWrapStreamBuilder() async {
Expression widgetExpr = flutter.identifyWidgetExpression(node);
if (widgetExpr == null) {
return;
}
if (flutter.isExactWidgetTypeStreamBuilder(widgetExpr.staticType)) {
return;
}
String widgetSrc = utils.getNodeText(widgetExpr);
var streamBuilderElement = await sessionHelper.getClass(
flutter.widgetsUri,
'StreamBuilder',
);
if (streamBuilderElement == null) {
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
builder.addReplacement(range.node(widgetExpr), (builder) {
builder.writeReference(streamBuilderElement);
builder.write('<');
builder.addSimpleLinkedEdit('type', 'Object');
builder.writeln('>(');
String indentOld = utils.getLinePrefix(widgetExpr.offset);
String indentNew1 = indentOld + utils.getIndent(1);
String indentNew2 = indentOld + utils.getIndent(2);
builder.write(indentNew1);
builder.writeln('stream: null,');
builder.write(indentNew1);
builder.writeln('builder: (context, snapshot) {');
widgetSrc = widgetSrc.replaceAll(
new RegExp("^$indentOld", multiLine: true),
indentNew2,
);
builder.write(indentNew2);
builder.write('return $widgetSrc');
builder.writeln(';');
builder.write(indentNew1);
builder.writeln('}');
builder.write(indentOld);
builder.write(')');
});
});
_addAssistFromBuilder(
changeBuilder,
DartAssistKind.FLUTTER_WRAP_STREAM_BUILDER,
);
}
Future<void> _addProposal_flutterWrapWidget() async {
await _addProposal_flutterWrapWidgetImpl();
await _addProposal_flutterWrapWidgetImpl(
kind: DartAssistKind.FLUTTER_WRAP_CENTER,
parentLibraryUri: flutter.widgetsUri,
parentClassName: 'Center',
widgetValidator: (expr) {
return !flutter.isExactWidgetTypeCenter(expr.staticType);
});
await _addProposal_flutterWrapWidgetImpl(
kind: DartAssistKind.FLUTTER_WRAP_CONTAINER,
parentLibraryUri: flutter.widgetsUri,
parentClassName: 'Container',
widgetValidator: (expr) {
return !flutter.isExactWidgetTypeContainer(expr.staticType);
});
await _addProposal_flutterWrapWidgetImpl(
kind: DartAssistKind.FLUTTER_WRAP_PADDING,
parentLibraryUri: flutter.widgetsUri,
parentClassName: 'Padding',
leadingLines: ['padding: const EdgeInsets.all(8.0),'],
widgetValidator: (expr) {
return !flutter.isExactWidgetTypePadding(expr.staticType);
});
}
Future<void> _addProposal_flutterWrapWidgetImpl(
{AssistKind kind = DartAssistKind.FLUTTER_WRAP_GENERIC,
bool Function(Expression widgetExpr) widgetValidator,
String parentLibraryUri,
String parentClassName,
List<String> leadingLines = const []}) async {
Expression widgetExpr = flutter.identifyWidgetExpression(node);
if (widgetExpr == null) {
_coverageMarker();
return;
}
if (widgetValidator != null && !widgetValidator(widgetExpr)) {
_coverageMarker();
return;
}
String widgetSrc = utils.getNodeText(widgetExpr);
// If the wrapper class is specified, find its element.
ClassElement parentClassElement;
if (parentLibraryUri != null && parentClassName != null) {
parentClassElement =
await sessionHelper.getClass(parentLibraryUri, parentClassName);
if (parentClassElement == null) {
return;
}
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(widgetExpr), (DartEditBuilder builder) {
if (parentClassElement == null) {
builder.addSimpleLinkedEdit('WIDGET', 'widget');
} else {
builder.writeReference(parentClassElement);
}
builder.write('(');
if (widgetSrc.contains(eol) || leadingLines.isNotEmpty) {
String indentOld = utils.getLinePrefix(widgetExpr.offset);
String indentNew = '$indentOld${utils.getIndent(1)}';
for (var leadingLine in leadingLines) {
builder.write(eol);
builder.write(indentNew);
builder.write(leadingLine);
}
builder.write(eol);
builder.write(indentNew);
widgetSrc = widgetSrc.replaceAll(
new RegExp("^$indentOld", multiLine: true), indentNew);
widgetSrc += ",$eol$indentOld";
}
if (parentClassElement == null) {
builder.addSimpleLinkedEdit('CHILD', 'child');
} else {
builder.write('child');
}
builder.write(': ');
builder.write(widgetSrc);
builder.write(')');
});
});
_addAssistFromBuilder(changeBuilder, kind);
}
Future<void> _addProposal_flutterWrapWidgets() async {
var selectionRange = new SourceRange(selectionOffset, selectionLength);
var analyzer = new SelectionAnalyzer(selectionRange);
context.resolveResult.unit.accept(analyzer);
List<Expression> widgetExpressions = [];
if (analyzer.hasSelectedNodes) {
for (var selectedNode in analyzer.selectedNodes) {
if (!flutter.isWidgetExpression(selectedNode)) {
return;
}
widgetExpressions.add(selectedNode);
}
} else {
var widget = flutter.identifyWidgetExpression(analyzer.coveringNode);
if (widget != null) {
widgetExpressions.add(widget);
}
}
if (widgetExpressions.isEmpty) {
return;
}
var firstWidget = widgetExpressions.first;
var lastWidget = widgetExpressions.last;
var selectedRange = range.startEnd(firstWidget, lastWidget);
String src = utils.getRangeText(selectedRange);
Future<void> addAssist(
{@required AssistKind kind,
@required String parentLibraryUri,
@required String parentClassName}) async {
ClassElement parentClassElement =
await sessionHelper.getClass(parentLibraryUri, parentClassName);
ClassElement widgetClassElement =
await sessionHelper.getClass(flutter.widgetsUri, 'Widget');
if (parentClassElement == null || widgetClassElement == null) {
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(selectedRange, (DartEditBuilder builder) {
builder.writeReference(parentClassElement);
builder.write('(');
String indentOld = utils.getLinePrefix(firstWidget.offset);
String indentNew1 = indentOld + utils.getIndent(1);
String indentNew2 = indentOld + utils.getIndent(2);
builder.write(eol);
builder.write(indentNew1);
builder.write('children: <');
builder.writeReference(widgetClassElement);
builder.write('>[');
builder.write(eol);
String newSrc = _replaceSourceIndent(src, indentOld, indentNew2);
builder.write(indentNew2);
builder.write(newSrc);
builder.write(',');
builder.write(eol);
builder.write(indentNew1);
builder.write('],');
builder.write(eol);
builder.write(indentOld);
builder.write(')');
});
});
_addAssistFromBuilder(changeBuilder, kind);
}
await addAssist(
kind: DartAssistKind.FLUTTER_WRAP_COLUMN,
parentLibraryUri: flutter.widgetsUri,
parentClassName: 'Column');
await addAssist(
kind: DartAssistKind.FLUTTER_WRAP_ROW,
parentLibraryUri: flutter.widgetsUri,
parentClassName: 'Row');
}
Future<void> _addProposal_importAddShow() async {
// prepare ImportDirective
ImportDirective importDirective =
node.thisOrAncestorOfType<ImportDirective>();
if (importDirective == null) {
_coverageMarker();
return;
}
// there should be no existing combinators
if (importDirective.combinators.isNotEmpty) {
_coverageMarker();
return;
}
// prepare whole import namespace
ImportElement importElement = importDirective.element;
if (importElement == null) {
_coverageMarker();
return;
}
Map<String, Element> namespace = getImportNamespace(importElement);
// prepare names of referenced elements (from this import)
SplayTreeSet<String> referencedNames = new SplayTreeSet<String>();
_SimpleIdentifierRecursiveAstVisitor visitor =
new _SimpleIdentifierRecursiveAstVisitor((SimpleIdentifier node) {
Element element = node.staticElement;
if (element != null && namespace[node.name] == element) {
referencedNames.add(element.displayName);
}
});
context.resolveResult.unit.accept(visitor);
// ignore if unused
if (referencedNames.isEmpty) {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
String showCombinator = ' show ${referencedNames.join(', ')}';
builder.addSimpleInsertion(importDirective.end - 1, showCombinator);
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.IMPORT_ADD_SHOW);
}
Future<void> _addProposal_inlineAdd() async {
final changeBuilder = await createBuilder_inlineAdd();
_addAssistFromBuilder(changeBuilder, DartAssistKind.INLINE_INVOCATION,
args: ['add']);
}
Future<void> _addProposal_introduceLocalTestedType() async {
AstNode node = this.node;
if (node is IfStatement) {
node = (node as IfStatement).condition;
} else if (node is WhileStatement) {
node = (node as WhileStatement).condition;
}
// prepare IsExpression
if (node is! IsExpression) {
_coverageMarker();
return;
}
IsExpression isExpression = node;
DartType castType = isExpression.type.type;
String castTypeCode = _getNodeText(isExpression.type);
// prepare environment
String indent = utils.getIndent(1);
String prefix;
Block targetBlock;
{
Statement statement = node.thisOrAncestorOfType<Statement>();
if (statement is IfStatement && statement.thenStatement is Block) {
targetBlock = statement.thenStatement;
} else if (statement is WhileStatement && statement.body is Block) {
targetBlock = statement.body;
} else {
_coverageMarker();
return;
}
prefix = utils.getNodePrefix(statement);
}
// prepare location
int offset;
String statementPrefix;
if (isExpression.notOperator == null) {
offset = targetBlock.leftBracket.end;
statementPrefix = indent;
} else {
offset = targetBlock.rightBracket.end;
statementPrefix = '';
}
// prepare excluded names
Set<String> excluded = new Set<String>();
ScopedNameFinder scopedNameFinder = new ScopedNameFinder(offset);
isExpression.accept(scopedNameFinder);
excluded.addAll(scopedNameFinder.locals.keys.toSet());
// name(s)
List<String> suggestions =
getVariableNameSuggestionsForExpression(castType, null, excluded);
if (suggestions.isNotEmpty) {
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addInsertion(offset, (DartEditBuilder builder) {
builder.write(eol + prefix + statementPrefix);
builder.write(castTypeCode);
builder.write(' ');
builder.addSimpleLinkedEdit('NAME', suggestions[0],
kind: LinkedEditSuggestionKind.VARIABLE,
suggestions: suggestions);
builder.write(' = ');
builder.write(_getNodeText(isExpression.expression));
builder.write(';');
builder.selectHere();
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.INTRODUCE_LOCAL_CAST_TYPE);
}
}
Future<void> _addProposal_invertIf() async {
if (node is! IfStatement) {
return;
}
IfStatement ifStatement = node as IfStatement;
Expression condition = ifStatement.condition;
// should have both "then" and "else"
Statement thenStatement = ifStatement.thenStatement;
Statement elseStatement = ifStatement.elseStatement;
if (thenStatement == null || elseStatement == null) {
return;
}
// prepare source
String invertedCondition = utils.invertCondition(condition);
String thenSource = _getNodeText(thenStatement);
String elseSource = _getNodeText(elseStatement);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(range.node(condition), invertedCondition);
builder.addSimpleReplacement(range.node(thenStatement), elseSource);
builder.addSimpleReplacement(range.node(elseStatement), thenSource);
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.INVERT_IF_STATEMENT);
}
Future<void> _addProposal_joinIfStatementInner() async {
// climb up condition to the (supposedly) "if" statement
AstNode node = this.node;
while (node is Expression) {
node = node.parent;
}
// prepare target "if" statement
if (node is! IfStatement) {
_coverageMarker();
return;
}
IfStatement targetIfStatement = node as IfStatement;
if (targetIfStatement.elseStatement != null) {
_coverageMarker();
return;
}
// prepare inner "if" statement
Statement targetThenStatement = targetIfStatement.thenStatement;
Statement innerStatement = getSingleStatement(targetThenStatement);
if (innerStatement is! IfStatement) {
_coverageMarker();
return;
}
IfStatement innerIfStatement = innerStatement as IfStatement;
if (innerIfStatement.elseStatement != null) {
_coverageMarker();
return;
}
// prepare environment
String prefix = utils.getNodePrefix(targetIfStatement);
// merge conditions
String condition;
{
Expression targetCondition = targetIfStatement.condition;
Expression innerCondition = innerIfStatement.condition;
String targetConditionSource = _getNodeText(targetCondition);
String innerConditionSource = _getNodeText(innerCondition);
if (_shouldWrapParenthesisBeforeAnd(targetCondition)) {
targetConditionSource = '($targetConditionSource)';
}
if (_shouldWrapParenthesisBeforeAnd(innerCondition)) {
innerConditionSource = '($innerConditionSource)';
}
condition = '$targetConditionSource && $innerConditionSource';
}
// replace target "if" statement
Statement innerThenStatement = innerIfStatement.thenStatement;
List<Statement> innerThenStatements = getStatements(innerThenStatement);
SourceRange lineRanges = utils.getLinesRangeStatements(innerThenStatements);
String oldSource = utils.getRangeText(lineRanges);
String newSource = utils.indentSourceLeftRight(oldSource);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(range.node(targetIfStatement),
'if ($condition) {$eol$newSource$prefix}');
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.JOIN_IF_WITH_INNER);
}
Future<void> _addProposal_joinIfStatementOuter() async {
// climb up condition to the (supposedly) "if" statement
AstNode node = this.node;
while (node is Expression) {
node = node.parent;
}
// prepare target "if" statement
if (node is! IfStatement) {
_coverageMarker();
return;
}
IfStatement targetIfStatement = node as IfStatement;
if (targetIfStatement.elseStatement != null) {
_coverageMarker();
return;
}
// prepare outer "if" statement
AstNode parent = targetIfStatement.parent;
if (parent is Block) {
if ((parent as Block).statements.length != 1) {
_coverageMarker();
return;
}
parent = parent.parent;
}
if (parent is! IfStatement) {
_coverageMarker();
return;
}
IfStatement outerIfStatement = parent as IfStatement;
if (outerIfStatement.elseStatement != null) {
_coverageMarker();
return;
}
// prepare environment
String prefix = utils.getNodePrefix(outerIfStatement);
// merge conditions
Expression targetCondition = targetIfStatement.condition;
Expression outerCondition = outerIfStatement.condition;
String targetConditionSource = _getNodeText(targetCondition);
String outerConditionSource = _getNodeText(outerCondition);
if (_shouldWrapParenthesisBeforeAnd(targetCondition)) {
targetConditionSource = '($targetConditionSource)';
}
if (_shouldWrapParenthesisBeforeAnd(outerCondition)) {
outerConditionSource = '($outerConditionSource)';
}
String condition = '$outerConditionSource && $targetConditionSource';
// replace outer "if" statement
Statement targetThenStatement = targetIfStatement.thenStatement;
List<Statement> targetThenStatements = getStatements(targetThenStatement);
SourceRange lineRanges =
utils.getLinesRangeStatements(targetThenStatements);
String oldSource = utils.getRangeText(lineRanges);
String newSource = utils.indentSourceLeftRight(oldSource);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(range.node(outerIfStatement),
'if ($condition) {$eol$newSource$prefix}');
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.JOIN_IF_WITH_OUTER);
}
Future<void> _addProposal_joinVariableDeclaration_onAssignment() async {
// check that node is LHS in assignment
if (node is SimpleIdentifier &&
node.parent is AssignmentExpression &&
(node.parent as AssignmentExpression).leftHandSide == node &&
node.parent.parent is ExpressionStatement) {
} else {
_coverageMarker();
return;
}
AssignmentExpression assignExpression = node.parent as AssignmentExpression;
// check that binary expression is assignment
if (assignExpression.operator.type != TokenType.EQ) {
_coverageMarker();
return;
}
// prepare "declaration" statement
Element element = (node as SimpleIdentifier).staticElement;
if (element == null) {
_coverageMarker();
return;
}
int declOffset = element.nameOffset;
var unit = context.resolveResult.unit;
AstNode declNode = new NodeLocator(declOffset).searchWithin(unit);
if (declNode != null &&
declNode.parent is VariableDeclaration &&
(declNode.parent as VariableDeclaration).name == declNode &&
declNode.parent.parent is VariableDeclarationList &&
declNode.parent.parent.parent is VariableDeclarationStatement) {
} else {
_coverageMarker();
return;
}
VariableDeclaration decl = declNode.parent as VariableDeclaration;
VariableDeclarationStatement declStatement =
decl.parent.parent as VariableDeclarationStatement;
// may be has initializer
if (decl.initializer != null) {
_coverageMarker();
return;
}
// check that "declaration" statement declared only one variable
if (declStatement.variables.variables.length != 1) {
_coverageMarker();
return;
}
// check that the "declaration" and "assignment" statements are
// parts of the same Block
ExpressionStatement assignStatement =
node.parent.parent as ExpressionStatement;
if (assignStatement.parent is Block &&
assignStatement.parent == declStatement.parent) {
} else {
_coverageMarker();
return;
}
Block block = assignStatement.parent as Block;
// check that "declaration" and "assignment" statements are adjacent
List<Statement> statements = block.statements;
if (statements.indexOf(assignStatement) ==
statements.indexOf(declStatement) + 1) {
} else {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(
range.endStart(declNode, assignExpression.operator), ' ');
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.JOIN_VARIABLE_DECLARATION);
}
Future<void> _addProposal_joinVariableDeclaration_onDeclaration() async {
// prepare enclosing VariableDeclarationList
VariableDeclarationList declList =
node.thisOrAncestorOfType<VariableDeclarationList>();
if (declList != null && declList.variables.length == 1) {
} else {
_coverageMarker();
return;
}
VariableDeclaration decl = declList.variables[0];
// already initialized
if (decl.initializer != null) {
_coverageMarker();
return;
}
// prepare VariableDeclarationStatement in Block
if (declList.parent is VariableDeclarationStatement &&
declList.parent.parent is Block) {
} else {
_coverageMarker();
return;
}
VariableDeclarationStatement declStatement =
declList.parent as VariableDeclarationStatement;
Block block = declStatement.parent as Block;
List<Statement> statements = block.statements;
// prepare assignment
AssignmentExpression assignExpression;
{
// declaration should not be last Statement
int declIndex = statements.indexOf(declStatement);
if (declIndex < statements.length - 1) {
} else {
_coverageMarker();
return;
}
// next Statement should be assignment
Statement assignStatement = statements[declIndex + 1];
if (assignStatement is ExpressionStatement) {
} else {
_coverageMarker();
return;
}
ExpressionStatement expressionStatement =
assignStatement as ExpressionStatement;
// expression should be assignment
if (expressionStatement.expression is AssignmentExpression) {
} else {
_coverageMarker();
return;
}
assignExpression = expressionStatement.expression as AssignmentExpression;
}
// check that pure assignment
if (assignExpression.operator.type != TokenType.EQ) {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(
range.endStart(decl.name, assignExpression.operator), ' ');
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.JOIN_VARIABLE_DECLARATION);
}
Future<void> _addProposal_removeTypeAnnotation() async {
// todo (pq): unify w/ fix (and then add a guard to not assist on lints:
// avoid_return_types_on_setters, type_init_formals)
final changeBuilder = await createBuilder_removeTypeAnnotation();
_addAssistFromBuilder(changeBuilder, DartAssistKind.REMOVE_TYPE_ANNOTATION);
}
Future<void> _addProposal_reparentFlutterList() async {
if (node is! ListLiteral) {
return;
}
if ((node as ListLiteral).elements.any((CollectionElement exp) =>
!(exp is InstanceCreationExpression &&
flutter.isWidgetCreation(exp)))) {
_coverageMarker();
return;
}
String literalSrc = utils.getNodeText(node);
int newlineIdx = literalSrc.lastIndexOf(eol);
if (newlineIdx < 0 || newlineIdx == literalSrc.length - 1) {
_coverageMarker();
return; // Lists need to be in multi-line format already.
}
String indentOld = utils.getLinePrefix(node.offset + 1 + newlineIdx);
String indentArg = '$indentOld${utils.getIndent(1)}';
String indentList = '$indentOld${utils.getIndent(2)}';
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(node), (DartEditBuilder builder) {
builder.write('[');
builder.write(eol);
builder.write(indentArg);
builder.addSimpleLinkedEdit('WIDGET', 'widget');
builder.write('(');
builder.write(eol);
builder.write(indentList);
// Linked editing not needed since arg is always a list.
builder.write('children: ');
builder.write(literalSrc.replaceAll(
new RegExp("^$indentOld", multiLine: true), "$indentList"));
builder.write(',');
builder.write(eol);
builder.write(indentArg);
builder.write('),');
builder.write(eol);
builder.write(indentOld);
builder.write(']');
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.FLUTTER_WRAP_GENERIC);
}
Future<void> _addProposal_replaceConditionalWithIfElse() async {
ConditionalExpression conditional = null;
// may be on Statement with Conditional
Statement statement = node.thisOrAncestorOfType<Statement>();
if (statement == null) {
_coverageMarker();
return;
}
// variable declaration
bool inVariable = false;
if (statement is VariableDeclarationStatement) {
VariableDeclarationStatement variableStatement = statement;
for (VariableDeclaration variable
in variableStatement.variables.variables) {
if (variable.initializer is ConditionalExpression) {
conditional = variable.initializer as ConditionalExpression;
inVariable = true;
break;
}
}
}
// assignment
bool inAssignment = false;
if (statement is ExpressionStatement) {
ExpressionStatement exprStmt = statement;
if (exprStmt.expression is AssignmentExpression) {
AssignmentExpression assignment =
exprStmt.expression as AssignmentExpression;
if (assignment.operator.type == TokenType.EQ &&
assignment.rightHandSide is ConditionalExpression) {
conditional = assignment.rightHandSide as ConditionalExpression;
inAssignment = true;
}
}
}
// return
bool inReturn = false;
if (statement is ReturnStatement) {
ReturnStatement returnStatement = statement;
if (returnStatement.expression is ConditionalExpression) {
conditional = returnStatement.expression as ConditionalExpression;
inReturn = true;
}
}
// prepare environment
String indent = utils.getIndent(1);
String prefix = utils.getNodePrefix(statement);
if (inVariable || inAssignment || inReturn) {
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
// Type v = Conditional;
if (inVariable) {
VariableDeclaration variable =
conditional.parent as VariableDeclaration;
builder.addDeletion(range.endEnd(variable.name, conditional));
String conditionSrc = _getNodeText(conditional.condition);
String thenSrc = _getNodeText(conditional.thenExpression);
String elseSrc = _getNodeText(conditional.elseExpression);
String name = variable.name.name;
String src = eol;
src += prefix + 'if ($conditionSrc) {' + eol;
src += prefix + indent + '$name = $thenSrc;' + eol;
src += prefix + '} else {' + eol;
src += prefix + indent + '$name = $elseSrc;' + eol;
src += prefix + '}';
builder.addSimpleReplacement(range.endLength(statement, 0), src);
}
// v = Conditional;
if (inAssignment) {
AssignmentExpression assignment =
conditional.parent as AssignmentExpression;
Expression leftSide = assignment.leftHandSide;
String conditionSrc = _getNodeText(conditional.condition);
String thenSrc = _getNodeText(conditional.thenExpression);
String elseSrc = _getNodeText(conditional.elseExpression);
String name = _getNodeText(leftSide);
String src = '';
src += 'if ($conditionSrc) {' + eol;
src += prefix + indent + '$name = $thenSrc;' + eol;
src += prefix + '} else {' + eol;
src += prefix + indent + '$name = $elseSrc;' + eol;
src += prefix + '}';
builder.addSimpleReplacement(range.node(statement), src);
}
// return Conditional;
if (inReturn) {
String conditionSrc = _getNodeText(conditional.condition);
String thenSrc = _getNodeText(conditional.thenExpression);
String elseSrc = _getNodeText(conditional.elseExpression);
String src = '';
src += 'if ($conditionSrc) {' + eol;
src += prefix + indent + 'return $thenSrc;' + eol;
src += prefix + '} else {' + eol;
src += prefix + indent + 'return $elseSrc;' + eol;
src += prefix + '}';
builder.addSimpleReplacement(range.node(statement), src);
}
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.REPLACE_CONDITIONAL_WITH_IF_ELSE);
}
}
Future<void> _addProposal_replaceIfElseWithConditional() async {
// should be "if"
if (node is! IfStatement) {
_coverageMarker();
return;
}
IfStatement ifStatement = node as IfStatement;
// single then/else statements
Statement thenStatement = getSingleStatement(ifStatement.thenStatement);
Statement elseStatement = getSingleStatement(ifStatement.elseStatement);
if (thenStatement == null || elseStatement == null) {
_coverageMarker();
return;
}
Expression thenExpression = null;
Expression elseExpression = null;
bool hasReturnStatements = false;
if (thenStatement is ReturnStatement && elseStatement is ReturnStatement) {
hasReturnStatements = true;
thenExpression = thenStatement.expression;
elseExpression = elseStatement.expression;
}
bool hasExpressionStatements = false;
if (thenStatement is ExpressionStatement &&
elseStatement is ExpressionStatement) {
if (thenStatement.expression is AssignmentExpression &&
elseStatement.expression is AssignmentExpression) {
hasExpressionStatements = true;
thenExpression = thenStatement.expression;
elseExpression = elseStatement.expression;
}
}
if (hasReturnStatements || hasExpressionStatements) {
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
// returns
if (hasReturnStatements) {
String conditionSrc = _getNodeText(ifStatement.condition);
String theSrc = _getNodeText(thenExpression);
String elseSrc = _getNodeText(elseExpression);
builder.addSimpleReplacement(range.node(ifStatement),
'return $conditionSrc ? $theSrc : $elseSrc;');
}
// assignments -> v = Conditional;
if (hasExpressionStatements) {
AssignmentExpression thenAssignment = thenExpression;
AssignmentExpression elseAssignment = elseExpression;
String thenTarget = _getNodeText(thenAssignment.leftHandSide);
String elseTarget = _getNodeText(elseAssignment.leftHandSide);
if (thenAssignment.operator.type == TokenType.EQ &&
elseAssignment.operator.type == TokenType.EQ &&
thenTarget == elseTarget) {
String conditionSrc = _getNodeText(ifStatement.condition);
String theSrc = _getNodeText(thenAssignment.rightHandSide);
String elseSrc = _getNodeText(elseAssignment.rightHandSide);
builder.addSimpleReplacement(range.node(ifStatement),
'$thenTarget = $conditionSrc ? $theSrc : $elseSrc;');
}
}
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL);
}
}
Future<void> _addProposal_sortChildPropertyLast() async {
final changeBuilder = await createBuilder_sortChildPropertyLast();
_addAssistFromBuilder(
changeBuilder, DartAssistKind.SORT_CHILD_PROPERTY_LAST);
}
Future<void> _addProposal_splitAndCondition() async {
// check that user invokes quick assist on binary expression
if (node is! BinaryExpression) {
_coverageMarker();
return;
}
BinaryExpression binaryExpression = node as BinaryExpression;
// prepare operator position
if (!_isOperatorSelected(
binaryExpression, selectionOffset, selectionLength)) {
_coverageMarker();
return;
}
// should be &&
if (binaryExpression.operator.type != TokenType.AMPERSAND_AMPERSAND) {
_coverageMarker();
return;
}
// prepare "if"
Statement statement = node.thisOrAncestorOfType<Statement>();
if (statement is! IfStatement) {
_coverageMarker();
return;
}
IfStatement ifStatement = statement as IfStatement;
// no support "else"
if (ifStatement.elseStatement != null) {
_coverageMarker();
return;
}
// check that binary expression is part of first level && condition of "if"
BinaryExpression condition = binaryExpression;
while (condition.parent is BinaryExpression &&
(condition.parent as BinaryExpression).operator.type ==
TokenType.AMPERSAND_AMPERSAND) {
condition = condition.parent as BinaryExpression;
}
if (ifStatement.condition != condition) {
_coverageMarker();
return;
}
// prepare environment
String prefix = utils.getNodePrefix(ifStatement);
String indent = utils.getIndent(1);
// prepare "rightCondition"
String rightConditionSource;
{
SourceRange rightConditionRange =
range.startEnd(binaryExpression.rightOperand, condition);
rightConditionSource = _getRangeText(rightConditionRange);
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
// remove "&& rightCondition"
builder
.addDeletion(range.endEnd(binaryExpression.leftOperand, condition));
// update "then" statement
Statement thenStatement = ifStatement.thenStatement;
if (thenStatement is Block) {
Block thenBlock = thenStatement;
SourceRange thenBlockRange = range.node(thenBlock);
// insert inner "if" with right part of "condition"
int thenBlockInsideOffset = thenBlockRange.offset + 1;
builder.addSimpleInsertion(thenBlockInsideOffset,
'$eol$prefix${indent}if ($rightConditionSource) {');
// insert closing "}" for inner "if"
int thenBlockEnd = thenBlockRange.end;
// insert before outer "then" block "}"
builder.addSimpleInsertion(thenBlockEnd - 1, '$indent}$eol$prefix');
} else {
// insert inner "if" with right part of "condition"
String source = '$eol$prefix${indent}if ($rightConditionSource)';
builder.addSimpleInsertion(
ifStatement.rightParenthesis.offset + 1, source);
}
// indent "then" statements to correspond inner "if"
{
List<Statement> thenStatements = getStatements(thenStatement);
SourceRange linesRange = utils.getLinesRangeStatements(thenStatements);
String thenIndentOld = '$prefix$indent';
String thenIndentNew = '$thenIndentOld$indent';
builder.addSimpleReplacement(
linesRange,
utils.replaceSourceRangeIndent(
linesRange, thenIndentOld, thenIndentNew));
}
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.SPLIT_AND_CONDITION);
}
Future<void> _addProposal_splitVariableDeclaration() async {
var variableList = node?.thisOrAncestorOfType<VariableDeclarationList>();
// Must be a local variable declaration.
if (variableList?.parent is! VariableDeclarationStatement) {
return;
}
VariableDeclarationStatement statement = variableList.parent;
// Cannot be `const` or `final`.
var keywordKind = variableList.keyword?.keyword;
if (keywordKind == Keyword.CONST || keywordKind == Keyword.FINAL) {
return;
}
var variables = variableList.variables;
if (variables.length != 1) {
return;
}
// The caret must be between the type and the variable name.
var variable = variables[0];
if (!range.startEnd(statement, variable.name).contains(selectionOffset)) {
return;
}
// The variable must have an initializer.
if (variable.initializer == null) {
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
if (variableList.type == null) {
builder.addReplacement(range.token(variableList.keyword), (builder) {
var type = variable.declaredElement.type;
builder.writeType(type);
});
}
var indent = utils.getNodePrefix(statement);
var name = variable.name.name;
builder.addSimpleInsertion(variable.name.end, ';' + eol + indent + name);
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.SPLIT_VARIABLE_DECLARATION);
}
Future<void> _addProposal_surroundWith() async {
// prepare selected statements
List<Statement> selectedStatements;
{
StatementAnalyzer selectionAnalyzer = new StatementAnalyzer(
context.resolveResult,
new SourceRange(selectionOffset, selectionLength));
selectionAnalyzer.analyze();
List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes;
// convert nodes to statements
selectedStatements = [];
for (AstNode selectedNode in selectedNodes) {
if (selectedNode is Statement) {
selectedStatements.add(selectedNode);
}
}
// we want only statements in blocks
for (var statement in selectedStatements) {
if (statement.parent is! Block) {
return;
}
}
// we want only statements
if (selectedStatements.isEmpty ||
selectedStatements.length != selectedNodes.length) {
return;
}
}
// prepare statement information
Statement firstStatement = selectedStatements[0];
SourceRange statementsRange =
utils.getLinesRangeStatements(selectedStatements);
// prepare environment
String indentOld = utils.getNodePrefix(firstStatement);
String indentNew = '$indentOld${utils.getIndent(1)}';
String indentedCode =
utils.replaceSourceRangeIndent(statementsRange, indentOld, indentNew);
// "block"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleInsertion(statementsRange.offset, '$indentOld{$eol');
builder.addSimpleReplacement(
statementsRange,
utils.replaceSourceRangeIndent(
statementsRange, indentOld, indentNew));
builder.addSimpleInsertion(statementsRange.end, '$indentOld}$eol');
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.SURROUND_WITH_BLOCK);
}
// "if"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(statementsRange, (DartEditBuilder builder) {
builder.write(indentOld);
builder.write('if (');
builder.addSimpleLinkedEdit('CONDITION', 'condition');
builder.write(') {');
builder.write(eol);
builder.write(indentedCode);
builder.write(indentOld);
builder.write('}');
builder.selectHere();
builder.write(eol);
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.SURROUND_WITH_IF);
}
// "while"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(statementsRange, (DartEditBuilder builder) {
builder.write(indentOld);
builder.write('while (');
builder.addSimpleLinkedEdit('CONDITION', 'condition');
builder.write(') {');
builder.write(eol);
builder.write(indentedCode);
builder.write(indentOld);
builder.write('}');
builder.selectHere();
builder.write(eol);
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.SURROUND_WITH_WHILE);
}
// "for-in"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(statementsRange, (DartEditBuilder builder) {
builder.write(indentOld);
builder.write('for (var ');
builder.addSimpleLinkedEdit('NAME', 'item');
builder.write(' in ');
builder.addSimpleLinkedEdit('ITERABLE', 'iterable');
builder.write(') {');
builder.write(eol);
builder.write(indentedCode);
builder.write(indentOld);
builder.write('}');
builder.selectHere();
builder.write(eol);
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.SURROUND_WITH_FOR_IN);
}
// "for"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(statementsRange, (DartEditBuilder builder) {
builder.write(indentOld);
builder.write('for (var ');
builder.addSimpleLinkedEdit('VAR', 'v');
builder.write(' = ');
builder.addSimpleLinkedEdit('INIT', 'init');
builder.write('; ');
builder.addSimpleLinkedEdit('CONDITION', 'condition');
builder.write('; ');
builder.addSimpleLinkedEdit('INCREMENT', 'increment');
builder.write(') {');
builder.write(eol);
builder.write(indentedCode);
builder.write(indentOld);
builder.write('}');
builder.selectHere();
builder.write(eol);
});
});
_addAssistFromBuilder(changeBuilder, DartAssistKind.SURROUND_WITH_FOR);
}
// "do-while"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(statementsRange, (DartEditBuilder builder) {
builder.write(indentOld);
builder.write('do {');
builder.write(eol);
builder.write(indentedCode);
builder.write(indentOld);
builder.write('} while (');
builder.addSimpleLinkedEdit('CONDITION', 'condition');
builder.write(');');
builder.selectHere();
builder.write(eol);
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.SURROUND_WITH_DO_WHILE);
}
// "try-catch"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(statementsRange, (DartEditBuilder builder) {
builder.write(indentOld);
builder.write('try {');
builder.write(eol);
builder.write(indentedCode);
builder.write(indentOld);
builder.write('} on ');
builder.addSimpleLinkedEdit('EXCEPTION_TYPE', 'Exception');
builder.write(' catch (');
builder.addSimpleLinkedEdit('EXCEPTION_VAR', 'e');
builder.write(') {');
builder.write(eol);
//
builder.write(indentNew);
builder.addSimpleLinkedEdit('CATCH', '// TODO');
builder.selectHere();
builder.write(eol);
//
builder.write(indentOld);
builder.write('}');
builder.write(eol);
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.SURROUND_WITH_TRY_CATCH);
}
// "try-finally"
{
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(statementsRange, (DartEditBuilder builder) {
builder.write(indentOld);
builder.write('try {');
builder.write(eol);
//
builder.write(indentedCode);
//
builder.write(indentOld);
builder.write('} finally {');
builder.write(eol);
//
builder.write(indentNew);
builder.addSimpleLinkedEdit('FINALLY', '// TODO');
builder.selectHere();
builder.write(eol);
//
builder.write(indentOld);
builder.write('}');
builder.write(eol);
});
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.SURROUND_WITH_TRY_FINALLY);
}
}
Future<void> _addProposal_useCurlyBraces() async {
final changeBuilder = await createBuilder_useCurlyBraces();
_addAssistFromBuilder(changeBuilder, DartAssistKind.USE_CURLY_BRACES);
}
Future<void> _addProposals_addTypeAnnotation() async {
var changeBuilder =
await createBuilder_addTypeAnnotation_DeclaredIdentifier();
_addAssistFromBuilder(changeBuilder, DartAssistKind.ADD_TYPE_ANNOTATION);
changeBuilder =
await createBuilder_addTypeAnnotation_SimpleFormalParameter();
_addAssistFromBuilder(changeBuilder, DartAssistKind.ADD_TYPE_ANNOTATION);
changeBuilder = await createBuilder_addTypeAnnotation_VariableDeclaration();
_addAssistFromBuilder(changeBuilder, DartAssistKind.ADD_TYPE_ANNOTATION);
}
/**
* Return `true` if all of the parameters in the given list of [parameters]
* have an explicit type annotation.
*/
bool _allParametersHaveTypes(FormalParameterList parameters) {
for (FormalParameter parameter in parameters.parameters) {
if (parameter is DefaultFormalParameter) {
parameter = (parameter as DefaultFormalParameter).parameter;
}
if (parameter is SimpleFormalParameter) {
if (parameter.type == null) {
return false;
}
} else if (parameter is! FunctionTypedFormalParameter) {
return false;
}
}
return true;
}
bool _containsErrorCode(Set<String> errorCodes) {
final fileOffset = node.offset;
for (var error in context.resolveResult.errors) {
final errorSource = error.source;
if (file == errorSource.fullName) {
if (fileOffset >= error.offset &&
fileOffset <= error.offset + error.length) {
if (errorCodes.contains(error.errorCode.name)) {
return true;
}
}
}
}
return false;
}
void _convertFlutterChildToChildren(
NamedExpression namedExp,
String eol,
Function getNodeText,
Function getLinePrefix,
Function getIndent,
Function getText,
DartFileEditBuilder builder) {
Expression childArg = namedExp.expression;
int childLoc = namedExp.offset + 'child'.length;
builder.addSimpleInsertion(childLoc, 'ren');
int listLoc = childArg.offset;
String childArgSrc = getNodeText(childArg);
if (!childArgSrc.contains(eol)) {
builder.addSimpleInsertion(listLoc, '<Widget>[');
builder.addSimpleInsertion(listLoc + childArg.length, ']');
} else {
int newlineLoc = childArgSrc.lastIndexOf(eol);
if (newlineLoc == childArgSrc.length) {
newlineLoc -= 1;
}
String indentOld = getLinePrefix(childArg.offset + 1 + newlineLoc);
String indentNew = '$indentOld${getIndent(1)}';
// The separator includes 'child:' but that has no newlines.
String separator =
getText(namedExp.offset, childArg.offset - namedExp.offset);
String prefix = separator.contains(eol) ? "" : "$eol$indentNew";
if (prefix.isEmpty) {
builder.addSimpleInsertion(
namedExp.offset + 'child:'.length, ' <Widget>[');
int argOffset = childArg.offset;
builder
.addDeletion(range.startOffsetEndOffset(argOffset - 2, argOffset));
} else {
builder.addSimpleInsertion(listLoc, '<Widget>[');
}
String newChildArgSrc =
_replaceSourceIndent(childArgSrc, indentOld, indentNew);
newChildArgSrc = "$prefix$newChildArgSrc,$eol$indentOld]";
builder.addSimpleReplacement(range.node(childArg), newChildArgSrc);
}
}
Future<void> _convertFunctionTypeAliasToGenericTypeAlias(
FunctionTypeAlias node) async {
if (!_allParametersHaveTypes(node.parameters)) {
return;
}
String returnType;
if (node.returnType != null) {
returnType = utils.getNodeText(node.returnType);
}
String functionName = utils.getRangeText(
range.startEnd(node.name, node.typeParameters ?? node.name));
String parameters = utils.getNodeText(node.parameters);
String replacement;
if (returnType == null) {
replacement = '$functionName = Function$parameters';
} else {
replacement = '$functionName = $returnType Function$parameters';
}
// add change
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(
range.startStart(node.typedefKeyword.next, node.semicolon),
replacement);
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_INTO_GENERIC_FUNCTION_SYNTAX);
}
Future<void> _convertFunctionTypedFormalParameterToSimpleFormalParameter(
FunctionTypedFormalParameter node) async {
if (!_allParametersHaveTypes(node.parameters)) {
return;
}
String returnType;
if (node.returnType != null) {
returnType = utils.getNodeText(node.returnType);
}
String functionName = utils.getRangeText(range.startEnd(
node.identifier, node.typeParameters ?? node.identifier));
String parameters = utils.getNodeText(node.parameters);
String replacement;
if (returnType == null) {
replacement = 'Function$parameters $functionName';
} else {
replacement = '$returnType Function$parameters $functionName';
}
// add change
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addSimpleReplacement(range.node(node), replacement);
});
_addAssistFromBuilder(
changeBuilder, DartAssistKind.CONVERT_INTO_GENERIC_FUNCTION_SYNTAX);
}
Future<void> _convertQuotes(bool fromDouble, AssistKind kind) async {
final changeBuilder = await createBuilder_convertQuotes(fromDouble);
_addAssistFromBuilder(changeBuilder, kind);
}
/**
* Returns the text of the given node in the unit.
*/
String _getNodeText(AstNode node) {
return utils.getNodeText(node);
}
/**
* Returns the text of the given range in the unit.
*/
String _getRangeText(SourceRange range) {
return utils.getRangeText(range);
}
/// Return `true` if the [element] is sufficient to lexically make the
/// enclosing literal a set literal rather than a map.
bool _isUnambiguousElement(CollectionElement element) {
if (element is ForElement) {
return _isUnambiguousElement(element.body);
} else if (element is IfElement) {
return _isUnambiguousElement(element.thenElement) ||
_isUnambiguousElement(element.elseElement);
} else if (element is Expression) {
return true;
}
return false;
}
/// Return `true` if the given [node] is a list literal whose elements, if
/// placed inside curly braces, would lexically make the resulting literal a
/// set literal rather than a map literal.
bool _listHasUnambiguousElement(AstNode node) {
if (node is ListLiteral && node.elements.isNotEmpty) {
for (CollectionElement element in node.elements) {
if (_isUnambiguousElement(element)) {
return true;
}
}
}
return false;
}
DartChangeBuilder _newDartChangeBuilder() {
return new DartChangeBuilderImpl.forWorkspace(context.workspace);
}
Future<void> _swapParentAndChild(InstanceCreationExpression parent,
InstanceCreationExpression child, AssistKind kind) async {
// The child must have its own child.
if (flutter.findChildArgument(child) == null) {
_coverageMarker();
return;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
builder.addReplacement(range.node(parent), (builder) {
var childArgs = child.argumentList;
var parentArgs = parent.argumentList;
var childText = _getRangeText(range.startStart(child, childArgs));
var parentText = _getRangeText(range.startStart(parent, parentArgs));
var parentIndent = utils.getLinePrefix(parent.offset);
var childIndent = parentIndent + ' ';
// Write the beginning of the child.
builder.write(childText);
builder.writeln('(');
// Write all the arguments of the parent.
// Don't write the "child".
Expression stableChild;
for (Expression argument in childArgs.arguments) {
if (flutter.isChildArgument(argument)) {
stableChild = argument;
} else {
String text = _getNodeText(argument);
text = _replaceSourceIndent(text, childIndent, parentIndent);
builder.write(parentIndent);
builder.write(' ');
builder.write(text);
builder.writeln(',');
}
}
// Write the parent as a new child.
builder.write(parentIndent);
builder.write(' ');
builder.write('child: ');
builder.write(parentText);
builder.writeln('(');
// Write all arguments of the parent.
// Don't write its child.
for (Expression argument in parentArgs.arguments) {
if (!flutter.isChildArgument(argument)) {
String text = _getNodeText(argument);
text = _replaceSourceIndent(text, parentIndent, childIndent);
builder.write(childIndent);
builder.write(' ');
builder.write(text);
builder.writeln(',');
}
}
// Write the child of the "child" now, as the child of the "parent".
{
var text = _getNodeText(stableChild);
builder.write(childIndent);
builder.write(' ');
builder.write(text);
builder.writeln(',');
}
// Close the parent expression.
builder.write(childIndent);
builder.writeln('),');
// Close the child expression.
builder.write(parentIndent);
builder.write(')');
});
});
_addAssistFromBuilder(changeBuilder, kind);
}
/**
* This method does nothing, but we invoke it in places where Dart VM
* coverage agent fails to provide coverage information - such as almost
* all "return" statements.
*
* https://code.google.com/p/dart/issues/detail?id=19912
*/
static void _coverageMarker() {}
/**
* Returns `true` if the selection covers an operator of the given
* [BinaryExpression].
*/
static bool _isOperatorSelected(
BinaryExpression binaryExpression, int offset, int length) {
AstNode left = binaryExpression.leftOperand;
AstNode right = binaryExpression.rightOperand;
// between the nodes
if (offset >= left.endToken.end && offset + length <= right.offset) {
_coverageMarker();
return true;
}
// or exactly select the node (but not with infix expressions)
if (offset == left.offset && offset + length == right.endToken.end) {
if (left is BinaryExpression || right is BinaryExpression) {
_coverageMarker();
return false;
}
_coverageMarker();
return true;
}
// invalid selection (part of node, etc)
_coverageMarker();
return false;
}
static String _replaceSourceIndent(
String source, String indentOld, String indentNew) {
return source.replaceAll(
new RegExp('^$indentOld', multiLine: true), indentNew);
}
/**
* Checks if the given [Expression] should be wrapped with parenthesis when we
* want to use it as operand of a logical `and` expression.
*/
static bool _shouldWrapParenthesisBeforeAnd(Expression expr) {
if (expr is BinaryExpression) {
BinaryExpression binary = expr;
int precedence = binary.operator.type.precedence;
return precedence < TokenClass.LOGICAL_AND_OPERATOR.precedence;
}
return false;
}
}
class _SimpleIdentifierRecursiveAstVisitor extends RecursiveAstVisitor {
final _SimpleIdentifierVisitor visitor;
_SimpleIdentifierRecursiveAstVisitor(this.visitor);
@override
visitSimpleIdentifier(SimpleIdentifier node) {
visitor(node);
}
}
/**
* A visitor used to find all of the classes that define members referenced via
* `super`.
*/
class _SuperclassReferenceFinder extends RecursiveAstVisitor {
final List<ClassElement> referencedClasses = <ClassElement>[];
_SuperclassReferenceFinder();
@override
visitSuperExpression(SuperExpression node) {
AstNode parent = node.parent;
if (parent is BinaryExpression) {
_addElement(parent.staticElement);
} else if (parent is IndexExpression) {
_addElement(parent.staticElement);
} else if (parent is MethodInvocation) {
_addIdentifier(parent.methodName);
} else if (parent is PrefixedIdentifier) {
_addIdentifier(parent.identifier);
} else if (parent is PropertyAccess) {
_addIdentifier(parent.propertyName);
}
return super.visitSuperExpression(node);
}
void _addElement(Element element) {
if (element is ExecutableElement) {
Element enclosingElement = element.enclosingElement;
if (enclosingElement is ClassElement) {
referencedClasses.add(enclosingElement);
}
}
}
void _addIdentifier(SimpleIdentifier identifier) {
_addElement(identifier.staticElement);
}
}