blob: a67b7ca94f74fda6fe67cc57d8f227c4c0a22e59 [file] [log] [blame]
// Copyright (c) 2019, 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 'package:analysis_server/src/services/correction/util.dart';
import 'package:analysis_server/src/utilities/flutter.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.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/type.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/analysis/session_helper.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
import 'package:meta/meta.dart';
/// Base class for common processor functionality.
abstract class BaseProcessor {
final int selectionOffset;
final int selectionLength;
final int selectionEnd;
final CorrectionUtils utils;
final String file;
final TypeProvider typeProvider;
final Flutter flutter;
final AnalysisSession session;
final AnalysisSessionHelper sessionHelper;
final ResolvedUnitResult resolvedResult;
final ChangeWorkspace workspace;
AstNode node;
BaseProcessor({
this.selectionOffset = -1,
this.selectionLength = 0,
@required this.resolvedResult,
@required this.workspace,
}) : file = resolvedResult.path,
flutter = Flutter.of(resolvedResult),
session = resolvedResult.session,
sessionHelper = AnalysisSessionHelper(resolvedResult.session),
typeProvider = resolvedResult.typeProvider,
selectionEnd = (selectionOffset ?? 0) + (selectionLength ?? 0),
utils = CorrectionUtils(resolvedResult);
/// Returns the EOL to use for this [CompilationUnit].
String get eol => utils.endOfLine;
/// Return the status of known experiments.
ExperimentStatus get experimentStatus =>
(session.analysisContext.analysisOptions as AnalysisOptionsImpl)
.experimentStatus;
Future<ChangeBuilder>
createBuilder_addTypeAnnotation_DeclaredIdentifier() async {
DeclaredIdentifier declaredIdentifier =
node.thisOrAncestorOfType<DeclaredIdentifier>();
if (declaredIdentifier == null) {
ForStatement forEach = node.thisOrAncestorMatching(
(node) => node is ForStatement && node.forLoopParts is ForEachParts);
ForEachParts forEachParts = forEach?.forLoopParts;
int offset = node.offset;
if (forEach != null &&
forEachParts.iterable != null &&
offset < forEachParts.iterable.offset) {
declaredIdentifier = forEachParts is ForEachPartsWithDeclaration
? forEachParts.loopVariable
: null;
}
}
if (declaredIdentifier == null) {
_coverageMarker();
return null;
}
// Ensure that there isn't already a type annotation.
if (declaredIdentifier.type != null) {
_coverageMarker();
return null;
}
DartType type = declaredIdentifier.identifier.staticType;
if (type is! InterfaceType && type is! FunctionType) {
_coverageMarker();
return null;
}
_configureTargetLocation(node);
var changeBuilder = _newDartChangeBuilder();
bool validChange = true;
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
Token keyword = declaredIdentifier.keyword;
if (keyword.keyword == Keyword.VAR) {
builder.addReplacement(range.token(keyword), (DartEditBuilder builder) {
validChange = builder.writeType(type);
});
} else {
builder.addInsertion(declaredIdentifier.identifier.offset,
(DartEditBuilder builder) {
validChange = builder.writeType(type);
builder.write(' ');
});
}
});
return validChange ? changeBuilder : null;
}
Future<ChangeBuilder>
createBuilder_addTypeAnnotation_SimpleFormalParameter() async {
AstNode node = this.node;
// should be the name of a simple parameter
if (node is! SimpleIdentifier || node.parent is! SimpleFormalParameter) {
_coverageMarker();
return null;
}
SimpleIdentifier name = node;
SimpleFormalParameter parameter = node.parent;
// the parameter should not have a type
if (parameter.type != null) {
_coverageMarker();
return null;
}
// prepare the type
DartType type = parameter.declaredElement.type;
// TODO(scheglov) If the parameter is in a method declaration, and if the
// method overrides a method that has a type for the corresponding
// parameter, it would be nice to copy down the type from the overridden
// method.
if (type is! InterfaceType) {
_coverageMarker();
return null;
}
// prepare type source
_configureTargetLocation(node);
var changeBuilder = _newDartChangeBuilder();
bool validChange = true;
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addInsertion(name.offset, (DartEditBuilder builder) {
validChange = builder.writeType(type);
builder.write(' ');
});
});
return validChange ? changeBuilder : null;
}
Future<ChangeBuilder> createBuilder_convertToNullAware() async {
AstNode node = this.node;
if (node is! ConditionalExpression) {
_coverageMarker();
return null;
}
ConditionalExpression conditional = node;
Expression condition = conditional.condition.unParenthesized;
SimpleIdentifier identifier;
Expression nullExpression;
Expression nonNullExpression;
int periodOffset;
if (condition is BinaryExpression) {
//
// Identify the variable being compared to `null`, or return if the
// condition isn't a simple comparison of `null` to a variable's value.
//
Expression leftOperand = condition.leftOperand;
Expression rightOperand = condition.rightOperand;
if (leftOperand is NullLiteral && rightOperand is SimpleIdentifier) {
identifier = rightOperand;
} else if (rightOperand is NullLiteral &&
leftOperand is SimpleIdentifier) {
identifier = leftOperand;
} else {
_coverageMarker();
return null;
}
if (identifier.staticElement is! LocalElement) {
_coverageMarker();
return null;
}
//
// Identify the expression executed when the variable is `null` and when
// it is non-`null`. Return if the `null` expression isn't a null literal
// or if the non-`null` expression isn't a method invocation whose target
// is the save variable being compared to `null`.
//
if (condition.operator.type == TokenType.EQ_EQ) {
nullExpression = conditional.thenExpression;
nonNullExpression = conditional.elseExpression;
} else if (condition.operator.type == TokenType.BANG_EQ) {
nonNullExpression = conditional.thenExpression;
nullExpression = conditional.elseExpression;
}
if (nullExpression == null || nonNullExpression == null) {
_coverageMarker();
return null;
}
if (nullExpression.unParenthesized is! NullLiteral) {
_coverageMarker();
return null;
}
Expression unwrappedExpression = nonNullExpression.unParenthesized;
Expression target;
Token operator;
if (unwrappedExpression is MethodInvocation) {
target = unwrappedExpression.target;
operator = unwrappedExpression.operator;
} else if (unwrappedExpression is PrefixedIdentifier) {
target = unwrappedExpression.prefix;
operator = unwrappedExpression.period;
} else {
_coverageMarker();
return null;
}
if (operator.type != TokenType.PERIOD) {
_coverageMarker();
return null;
}
if (!(target is SimpleIdentifier &&
target.staticElement == identifier.staticElement)) {
_coverageMarker();
return null;
}
periodOffset = operator.offset;
DartChangeBuilder changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addDeletion(range.startStart(node, nonNullExpression));
builder.addSimpleInsertion(periodOffset, '?');
builder.addDeletion(range.endEnd(nonNullExpression, node));
});
return changeBuilder;
}
return null;
}
Future<ChangeBuilder> createBuilder_convertToExpressionFunctionBody() async {
// prepare current body
FunctionBody body = getEnclosingFunctionBody();
if (body is! BlockFunctionBody || body.isGenerator) {
_coverageMarker();
return null;
}
// prepare return statement
List<Statement> statements = (body as BlockFunctionBody).block.statements;
if (statements.length != 1) {
_coverageMarker();
return null;
}
Statement onlyStatement = statements.first;
// prepare returned expression
Expression returnExpression;
if (onlyStatement is ReturnStatement) {
returnExpression = onlyStatement.expression;
} else if (onlyStatement is ExpressionStatement) {
returnExpression = onlyStatement.expression;
}
if (returnExpression == null) {
_coverageMarker();
return null;
}
// 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 >= returnExpression.offset) {
_coverageMarker();
return null;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(body), (DartEditBuilder builder) {
if (body.isAsynchronous) {
builder.write('async ');
}
builder.write('=> ');
builder.write(_getNodeText(returnExpression));
if (body.parent is! FunctionExpression ||
body.parent.parent is FunctionDeclaration) {
builder.write(';');
}
});
});
return changeBuilder;
}
/// Returns the text of the given node in the unit.
String /* TODO (pq): make visible */ _getNodeText(AstNode node) =>
utils.getNodeText(node);
FunctionBody getEnclosingFunctionBody() {
// TODO(brianwilkerson) Determine whether there is a reason why this method
// isn't just "return node.getAncestor((node) => node is FunctionBody);"
{
FunctionExpression function =
node.thisOrAncestorOfType<FunctionExpression>();
if (function != null) {
return function.body;
}
}
{
FunctionDeclaration function =
node.thisOrAncestorOfType<FunctionDeclaration>();
if (function != null) {
return function.functionExpression.body;
}
}
{
ConstructorDeclaration constructor =
node.thisOrAncestorOfType<ConstructorDeclaration>();
if (constructor != null) {
return constructor.body;
}
}
{
MethodDeclaration method = node.thisOrAncestorOfType<MethodDeclaration>();
if (method != null) {
return method.body;
}
}
return null;
}
Future<ChangeBuilder>
createBuilder_addTypeAnnotation_VariableDeclaration() async {
AstNode node = this.node;
// prepare VariableDeclarationList
VariableDeclarationList declarationList =
node.thisOrAncestorOfType<VariableDeclarationList>();
if (declarationList == null) {
_coverageMarker();
return null;
}
// may be has type annotation already
if (declarationList.type != null) {
_coverageMarker();
return null;
}
// prepare single VariableDeclaration
List<VariableDeclaration> variables = declarationList.variables;
if (variables.length != 1) {
_coverageMarker();
return null;
}
VariableDeclaration variable = variables[0];
// must be not after the name of the variable
if (selectionOffset > variable.name.end) {
_coverageMarker();
return null;
}
// we need an initializer to get the type from
Expression initializer = variable.initializer;
if (initializer == null) {
_coverageMarker();
return null;
}
DartType type = initializer.staticType;
// prepare type source
if ((type is! InterfaceType || type.isDartCoreNull) &&
type is! FunctionType) {
_coverageMarker();
return null;
}
_configureTargetLocation(node);
var changeBuilder = _newDartChangeBuilder();
bool validChange = true;
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
Token keyword = declarationList.keyword;
if (keyword?.keyword == Keyword.VAR) {
builder.addReplacement(range.token(keyword), (DartEditBuilder builder) {
validChange = builder.writeType(type);
});
} else {
builder.addInsertion(variable.offset, (DartEditBuilder builder) {
validChange = builder.writeType(type);
builder.write(' ');
});
}
});
return validChange ? changeBuilder : null;
}
Future<ChangeBuilder>
createBuilder_convertConditionalExpressionToIfElement() async {
AstNode node = this.node.thisOrAncestorOfType<ConditionalExpression>();
if (node == null) {
_coverageMarker();
return null;
}
AstNode nodeToReplace = node;
AstNode parent = node.parent;
while (parent is ParenthesizedExpression) {
nodeToReplace = parent;
parent = parent.parent;
}
if (parent is ListLiteral || (parent is SetOrMapLiteral && parent.isSet)) {
ConditionalExpression conditional = node;
Expression condition = conditional.condition.unParenthesized;
Expression thenExpression = conditional.thenExpression.unParenthesized;
Expression elseExpression = conditional.elseExpression.unParenthesized;
DartChangeBuilder changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(nodeToReplace),
(DartEditBuilder builder) {
builder.write('if (');
builder.write(utils.getNodeText(condition));
builder.write(') ');
builder.write(utils.getNodeText(thenExpression));
builder.write(' else ');
builder.write(utils.getNodeText(elseExpression));
});
});
return changeBuilder;
}
return null;
}
Future<ChangeBuilder> createBuilder_convertDocumentationIntoLine() async {
Comment comment = node.thisOrAncestorOfType<Comment>();
if (comment == null ||
!comment.isDocumentation ||
comment.tokens.length != 1) {
_coverageMarker();
return null;
}
Token token = comment.tokens.first;
if (token.type != TokenType.MULTI_LINE_COMMENT) {
_coverageMarker();
return null;
}
String text = token.lexeme;
List<String> lines = text.split('\n');
String prefix = utils.getNodePrefix(comment);
List<String> newLines = <String>[];
bool firstLine = true;
String linePrefix = '';
for (String line in lines) {
if (firstLine) {
firstLine = false;
String expectedPrefix = '/**';
if (!line.startsWith(expectedPrefix)) {
_coverageMarker();
return null;
}
line = line.substring(expectedPrefix.length).trim();
if (line.isNotEmpty) {
newLines.add('/// $line');
linePrefix = eol + prefix;
}
} else {
if (line.startsWith(prefix + ' */')) {
break;
}
String expectedPrefix = prefix + ' *';
if (!line.startsWith(expectedPrefix)) {
_coverageMarker();
return null;
}
line = line.substring(expectedPrefix.length);
if (line.isEmpty) {
newLines.add('$linePrefix///');
} else {
newLines.add('$linePrefix///$line');
}
linePrefix = eol + prefix;
}
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(comment), (DartEditBuilder builder) {
for (String newLine in newLines) {
builder.write(newLine);
}
});
});
return changeBuilder;
}
Future<ChangeBuilder>
createBuilder_convertMapFromIterableToForLiteral() async {
//
// Ensure that the selection is inside an invocation of Map.fromIterable.
//
InstanceCreationExpression creation =
node.thisOrAncestorOfType<InstanceCreationExpression>();
if (creation == null) {
_coverageMarker();
return null;
}
ConstructorElement element = creation.staticElement;
if (element == null ||
element.name != 'fromIterable' ||
element.enclosingElement != typeProvider.mapType.element) {
_coverageMarker();
return null;
}
//
// Ensure that the arguments have the right form.
//
NodeList<Expression> arguments = creation.argumentList.arguments;
if (arguments.length != 3) {
_coverageMarker();
return null;
}
Expression iterator = arguments[0].unParenthesized;
Expression secondArg = arguments[1];
Expression thirdArg = arguments[2];
Expression extractBody(FunctionExpression expression) {
FunctionBody body = expression.body;
if (body is ExpressionFunctionBody) {
return body.expression;
} else if (body is BlockFunctionBody) {
NodeList<Statement> statements = body.block.statements;
if (statements.length == 1) {
Statement statement = statements[0];
if (statement is ReturnStatement) {
return statement.expression;
}
}
}
return null;
}
FunctionExpression extractClosure(String name, Expression argument) {
if (argument is NamedExpression && argument.name.label.name == name) {
Expression expression = argument.expression.unParenthesized;
if (expression is FunctionExpression) {
NodeList<FormalParameter> parameters =
expression.parameters.parameters;
if (parameters.length == 1 && parameters[0].isRequiredPositional) {
if (extractBody(expression) != null) {
return expression;
}
}
}
}
return null;
}
FunctionExpression keyClosure =
extractClosure('key', secondArg) ?? extractClosure('key', thirdArg);
FunctionExpression valueClosure =
extractClosure('value', thirdArg) ?? extractClosure('value', secondArg);
if (keyClosure == null || valueClosure == null) {
_coverageMarker();
return null;
}
//
// Compute the loop variable name and convert the key and value closures if
// necessary.
//
SimpleFormalParameter keyParameter = keyClosure.parameters.parameters[0];
String keyParameterName = keyParameter.identifier.name;
SimpleFormalParameter valueParameter =
valueClosure.parameters.parameters[0];
String valueParameterName = valueParameter.identifier.name;
Expression keyBody = extractBody(keyClosure);
String keyExpressionText = utils.getNodeText(keyBody);
Expression valueBody = extractBody(valueClosure);
String valueExpressionText = utils.getNodeText(valueBody);
String loopVariableName;
if (keyParameterName == valueParameterName) {
loopVariableName = keyParameterName;
} else {
_ParameterReferenceFinder keyFinder =
new _ParameterReferenceFinder(keyParameter.declaredElement);
keyBody.accept(keyFinder);
_ParameterReferenceFinder valueFinder =
new _ParameterReferenceFinder(valueParameter.declaredElement);
valueBody.accept(valueFinder);
String computeUnusedVariableName() {
String candidate = 'e';
var index = 1;
while (keyFinder.referencesName(candidate) ||
valueFinder.referencesName(candidate)) {
candidate = 'e${index++}';
}
return candidate;
}
if (valueFinder.isParameterUnreferenced) {
if (valueFinder.referencesName(keyParameterName)) {
// The name of the value parameter is not used, but we can't use the
// name of the key parameter because doing so would hide a variable
// referenced in the value expression.
loopVariableName = computeUnusedVariableName();
keyExpressionText = keyFinder.replaceName(
keyExpressionText, loopVariableName, keyBody.offset);
} else {
loopVariableName = keyParameterName;
}
} else if (keyFinder.isParameterUnreferenced) {
if (keyFinder.referencesName(valueParameterName)) {
// The name of the key parameter is not used, but we can't use the
// name of the value parameter because doing so would hide a variable
// referenced in the key expression.
loopVariableName = computeUnusedVariableName();
valueExpressionText = valueFinder.replaceName(
valueExpressionText, loopVariableName, valueBody.offset);
} else {
loopVariableName = valueParameterName;
}
} else {
// The names are different and both are used. We need to find a name
// that would not change the resolution of any other identifiers in
// either the key or value expressions.
loopVariableName = computeUnusedVariableName();
keyExpressionText = keyFinder.replaceName(
keyExpressionText, loopVariableName, keyBody.offset);
valueExpressionText = valueFinder.replaceName(
valueExpressionText, loopVariableName, valueBody.offset);
}
}
//
// Construct the edit.
//
DartChangeBuilder changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(range.node(creation), (DartEditBuilder builder) {
builder.write('{ for (var ');
builder.write(loopVariableName);
builder.write(' in ');
builder.write(utils.getNodeText(iterator));
builder.write(') ');
builder.write(keyExpressionText);
builder.write(' : ');
builder.write(valueExpressionText);
builder.write(' }');
});
});
return changeBuilder;
}
Future<ChangeBuilder> createBuilder_convertToIntLiteral() async {
if (node is! DoubleLiteral) {
_coverageMarker();
return null;
}
DoubleLiteral literal = node;
int intValue;
try {
intValue = literal.value?.truncate();
} catch (e) {
// Double cannot be converted to int
}
if (intValue == null || intValue != literal.value) {
_coverageMarker();
return null;
}
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addReplacement(new SourceRange(literal.offset, literal.length),
(DartEditBuilder builder) {
builder.write('$intValue');
});
});
return changeBuilder;
}
@protected
Future<ChangeBuilder> createBuilder_useCurlyBraces() async {
Future<ChangeBuilder> doStatement(DoStatement node) async {
var body = node.body;
if (body is Block) return null;
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
builder.addSimpleReplacement(
range.endStart(node.doKeyword, body),
' {$eol$indent',
);
builder.addSimpleReplacement(
range.endStart(body, node.whileKeyword),
'$eol$prefix} ',
);
});
return changeBuilder;
}
Future<ChangeBuilder> forStatement(ForStatement node) async {
var body = node.body;
if (body is Block) return null;
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
builder.addSimpleReplacement(
range.endStart(node.rightParenthesis, body),
' {$eol$indent',
);
builder.addSimpleInsertion(body.end, '$eol$prefix}');
});
return changeBuilder;
}
Future<ChangeBuilder> ifStatement(
IfStatement node, Statement thenOrElse) async {
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
var thenStatement = node.thenStatement;
if (thenStatement is! Block &&
(thenOrElse == null || thenOrElse == thenStatement)) {
builder.addSimpleReplacement(
range.endStart(node.rightParenthesis, thenStatement),
' {$eol$indent',
);
if (node.elseKeyword != null) {
builder.addSimpleReplacement(
range.endStart(thenStatement, node.elseKeyword),
'$eol$prefix} ',
);
} else {
builder.addSimpleInsertion(thenStatement.end, '$eol$prefix}');
}
}
var elseStatement = node.elseStatement;
if (elseStatement != null &&
elseStatement is! Block &&
(thenOrElse == null || thenOrElse == elseStatement)) {
builder.addSimpleReplacement(
range.endStart(node.elseKeyword, elseStatement),
' {$eol$indent',
);
builder.addSimpleInsertion(elseStatement.end, '$eol$prefix}');
}
});
return changeBuilder;
}
Future<ChangeBuilder> whileStatement(WhileStatement node) async {
var body = node.body;
if (body is Block) return null;
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var changeBuilder = _newDartChangeBuilder();
await changeBuilder.addFileEdit(file, (builder) {
builder.addSimpleReplacement(
range.endStart(node.rightParenthesis, body),
' {$eol$indent',
);
builder.addSimpleInsertion(body.end, '$eol$prefix}');
});
return changeBuilder;
}
var statement = this.node.thisOrAncestorOfType<Statement>();
var parent = statement?.parent;
if (statement is DoStatement) {
return doStatement(statement);
} else if (parent is DoStatement) {
return doStatement(parent);
} else if (statement is ForStatement) {
return forStatement(statement);
} else if (parent is ForStatement) {
return forStatement(parent);
} else if (statement is IfStatement) {
if (statement.elseKeyword != null &&
range.token(statement.elseKeyword).contains(selectionOffset)) {
return ifStatement(statement, statement.elseStatement);
} else {
return ifStatement(statement, null);
}
} else if (parent is IfStatement) {
return ifStatement(parent, statement);
} else if (statement is WhileStatement) {
return whileStatement(statement);
} else if (parent is WhileStatement) {
return whileStatement(parent);
}
return null;
}
@protected
bool setupCompute() {
final locator = NodeLocator(selectionOffset, selectionEnd);
node = locator.searchWithin(resolvedResult.unit);
return node != null;
}
/// Configures [utils] using given [target].
void _configureTargetLocation(Object target) {
utils.targetClassElement = null;
if (target is AstNode) {
ClassDeclaration targetClassDeclaration =
target.thisOrAncestorOfType<ClassDeclaration>();
if (targetClassDeclaration != null) {
utils.targetClassElement = targetClassDeclaration.declaredElement;
}
}
}
DartChangeBuilder _newDartChangeBuilder() =>
DartChangeBuilderImpl.forWorkspace(workspace);
/// 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() {}
}
/// A visitor that can be used to find references to a parameter.
class _ParameterReferenceFinder extends RecursiveAstVisitor<void> {
/// The parameter for which references are being sought, or `null` if we are
/// just accumulating a list of referenced names.
final ParameterElement parameter;
/// A list of the simple identifiers that reference the [parameter].
final List<SimpleIdentifier> references = <SimpleIdentifier>[];
/// A collection of the names of other simple identifiers that were found. We
/// need to know these in order to ensure that the selected loop variable does
/// not hide a name from an enclosing scope that is already being referenced.
final Set<String> otherNames = new Set<String>();
/// Initialize a newly created finder to find references to the [parameter].
_ParameterReferenceFinder(this.parameter) : assert(parameter != null);
/// Return `true` if the parameter is unreferenced in the nodes that have been
/// visited.
bool get isParameterUnreferenced => references.isEmpty;
/// Return `true` is the given name (assumed to be different than the name of
/// the parameter) is references in the nodes that have been visited.
bool referencesName(String name) => otherNames.contains(name);
/// Replace all of the references to the parameter in the given [source] with
/// the [newName]. The [offset] is the offset of the first character of the
/// [source] relative to the start of the file.
String replaceName(String source, String newName, int offset) {
int oldLength = parameter.name.length;
for (int i = references.length - 1; i >= 0; i--) {
int oldOffset = references[i].offset - offset;
source = source.replaceRange(oldOffset, oldOffset + oldLength, newName);
}
return source;
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.staticElement == parameter) {
references.add(node);
} else if (!node.isQualified) {
// Only non-prefixed identifiers can be hidden.
otherNames.add(node.name);
}
}
}