blob: 5bd6ce26ed749bfd08d8ee2522b4c9c155117889 [file] [log] [blame]
// Copyright (c) 2020, 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:core';
import 'package:analysis_server/plugin/edit/fix/fix_dart.dart';
import 'package:analysis_server/src/services/correction/change_workspace.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/dart/add_await.dart';
import 'package:analysis_server/src/services/correction/dart/add_const.dart';
import 'package:analysis_server/src/services/correction/dart/add_diagnostic_property_reference.dart';
import 'package:analysis_server/src/services/correction/dart/add_override.dart';
import 'package:analysis_server/src/services/correction/dart/convert_add_all_to_spread.dart';
import 'package:analysis_server/src/services/correction/dart/convert_conditional_expression_to_if_element.dart';
import 'package:analysis_server/src/services/correction/dart/convert_documentation_into_line.dart';
import 'package:analysis_server/src/services/correction/dart/convert_map_from_iterable_to_for_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_quotes.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_contains.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_generic_function_syntax.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_if_null.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_int_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_list_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_map_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_relative_import.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_set_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_where_type.dart';
import 'package:analysis_server/src/services/correction/dart/create_method.dart';
import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
import 'package:analysis_server/src/services/correction/dart/inline_invocation.dart';
import 'package:analysis_server/src/services/correction/dart/make_final.dart';
import 'package:analysis_server/src/services/correction/dart/remove_argument.dart';
import 'package:analysis_server/src/services/correction/dart/remove_await.dart';
import 'package:analysis_server/src/services/correction/dart/remove_const.dart';
import 'package:analysis_server/src/services/correction/dart/remove_duplicate_case.dart';
import 'package:analysis_server/src/services/correction/dart/remove_empty_catch.dart';
import 'package:analysis_server/src/services/correction/dart/remove_empty_constructor_body.dart';
import 'package:analysis_server/src/services/correction/dart/remove_empty_else.dart';
import 'package:analysis_server/src/services/correction/dart/remove_empty_statement.dart';
import 'package:analysis_server/src/services/correction/dart/remove_initializer.dart';
import 'package:analysis_server/src/services/correction/dart/remove_interpolation_braces.dart';
import 'package:analysis_server/src/services/correction/dart/remove_method_declaration.dart';
import 'package:analysis_server/src/services/correction/dart/remove_operator.dart';
import 'package:analysis_server/src/services/correction/dart/remove_this_expression.dart';
import 'package:analysis_server/src/services/correction/dart/remove_type_annotation.dart';
import 'package:analysis_server/src/services/correction/dart/remove_unnecessary_new.dart';
import 'package:analysis_server/src/services/correction/dart/rename_to_camel_case.dart';
import 'package:analysis_server/src/services/correction/dart/replace_cascade_with_dot.dart';
import 'package:analysis_server/src/services/correction/dart/replace_colon_with_equals.dart';
import 'package:analysis_server/src/services/correction/dart/replace_final_with_const.dart';
import 'package:analysis_server/src/services/correction/dart/replace_null_with_closure.dart';
import 'package:analysis_server/src/services/correction/dart/replace_with_conditional_assignment.dart';
import 'package:analysis_server/src/services/correction/dart/replace_with_is_empty.dart';
import 'package:analysis_server/src/services/correction/dart/replace_with_tear_off.dart';
import 'package:analysis_server/src/services/correction/dart/replace_with_var.dart';
import 'package:analysis_server/src/services/correction/dart/sort_child_property_last.dart';
import 'package:analysis_server/src/services/correction/dart/use_curly_braces.dart';
import 'package:analysis_server/src/services/correction/dart/use_is_not_empty.dart';
import 'package:analysis_server/src/services/correction/dart/use_rethrow.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/source/error_processor.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
/// A fix producer that produces changes to fix multiple diagnostics.
class BulkFixProcessor {
/// A map from the name of a lint rule to a list of generators used to create
/// the correction producer used to build a fix for that diagnostic. The
/// generators used for non-lint diagnostics are in the [nonLintProducerMap].
///
/// Most entries will have only one generator. In cases where there is more
/// than one, they will be applied in series and the expectation is that only
/// one will produce a change for a given fix. If more than one change is
/// produced the result will almost certainly be invalid code.
static const Map<String, List<ProducerGenerator>> lintProducerMap = {
LintNames.annotate_overrides: [
AddOverride.newInstance,
],
LintNames.avoid_annotating_with_dynamic: [
RemoveTypeAnnotation.newInstance,
],
LintNames.avoid_empty_else: [
RemoveEmptyElse.newInstance,
],
LintNames.avoid_init_to_null: [
RemoveInitializer.newInstance,
],
LintNames.avoid_redundant_argument_values: [
RemoveArgument.newInstance,
],
LintNames.avoid_return_types_on_setters: [
RemoveTypeAnnotation.newInstance,
],
LintNames.avoid_single_cascade_in_expression_statements: [
ReplaceCascadeWithDot.newInstance,
],
LintNames.avoid_types_on_closure_parameters: [
RemoveTypeAnnotation.newInstance,
],
LintNames.await_only_futures: [
RemoveAwait.newInstance,
],
LintNames.curly_braces_in_flow_control_structures: [
UseCurlyBraces.newInstance,
],
LintNames.diagnostic_describe_all_properties: [
AddDiagnosticPropertyReference.newInstance,
],
LintNames.empty_catches: [
RemoveEmptyCatch.newInstance,
],
LintNames.empty_constructor_bodies: [
RemoveEmptyConstructorBody.newInstance,
],
LintNames.empty_statements: [
RemoveEmptyStatement.newInstance,
],
LintNames.hash_and_equals: [CreateMethod.equalsOrHashCode],
LintNames.no_duplicate_case_values: [
RemoveDuplicateCase.newInstance,
],
LintNames.non_constant_identifier_names: [
RenameToCamelCase.newInstance,
],
LintNames.null_closures: [
ReplaceNullWithClosure.newInstance,
],
LintNames.omit_local_variable_types: [
ReplaceWithVar.newInstance,
],
LintNames.prefer_adjacent_string_concatenation: [
RemoveOperator.newInstance,
],
LintNames.prefer_collection_literals: [
ConvertToListLiteral.newInstance,
ConvertToMapLiteral.newInstance,
ConvertToSetLiteral.newInstance,
],
LintNames.prefer_conditional_assignment: [
ReplaceWithConditionalAssignment.newInstance,
],
// TODO (pq): can produce results incompatible w/ `unnecessary_const`
// LintNames.prefer_const_constructors: [
// AddConst.newInstance,
// ReplaceNewWithConst.newInstance,
// ],
LintNames.prefer_const_constructors_in_immutables: [
AddConst.newInstance,
],
LintNames.prefer_const_declarations: [
ReplaceFinalWithConst.newInstance,
],
LintNames.prefer_contains: [
ConvertToContains.newInstance,
],
LintNames.prefer_equal_for_default_values: [
ReplaceColonWithEquals.newInstance,
],
LintNames.prefer_final_fields: [
MakeFinal.newInstance,
],
LintNames.prefer_final_locals: [
MakeFinal.newInstance,
],
LintNames.prefer_for_elements_to_map_fromIterable: [
ConvertMapFromIterableToForLiteral.newInstance,
],
LintNames.prefer_generic_function_type_aliases: [
ConvertToGenericFunctionSyntax.newInstance,
],
LintNames.prefer_if_elements_to_conditional_expressions: [
ConvertConditionalExpressionToIfElement.newInstance,
],
LintNames.prefer_if_null_operators: [
ConvertToIfNull.newInstance,
],
LintNames.prefer_inlined_adds: [
ConvertAddAllToSpread.newInstance,
InlineInvocation.newInstance,
],
LintNames.prefer_int_literals: [
ConvertToIntLiteral.newInstance,
],
LintNames.prefer_is_empty: [
ReplaceWithIsEmpty.newInstance,
],
LintNames.prefer_is_not_empty: [
UesIsNotEmpty.newInstance,
],
LintNames.prefer_iterable_whereType: [
ConvertToWhereType.newInstance,
],
LintNames.prefer_null_aware_operators: [
ConvertToNullAware.newInstance,
],
LintNames.prefer_relative_imports: [
ConvertToRelativeImport.newInstance,
],
LintNames.prefer_single_quotes: [
ConvertToSingleQuotes.newInstance,
],
LintNames.prefer_spread_collections: [
ConvertAddAllToSpread.newInstance,
],
LintNames.slash_for_doc_comments: [
ConvertDocumentationIntoLine.newInstance,
],
LintNames.sort_child_properties_last: [
SortChildPropertyLast.newInstance,
],
LintNames.type_init_formals: [
RemoveTypeAnnotation.newInstance,
],
LintNames.unawaited_futures: [
AddAwait.newInstance,
],
LintNames.unnecessary_brace_in_string_interps: [
RemoveInterpolationBraces.newInstance,
],
LintNames.unnecessary_const: [
RemoveUnnecessaryConst.newInstance,
],
LintNames.unnecessary_lambdas: [
ReplaceWithTearOff.newInstance,
],
LintNames.unnecessary_new: [
RemoveUnnecessaryNew.newInstance,
],
LintNames.unnecessary_overrides: [
RemoveMethodDeclaration.newInstance,
],
LintNames.unnecessary_this: [
RemoveThisExpression.newInstance,
],
LintNames.use_rethrow_when_possible: [
UseRethrow.newInstance,
],
};
/// A map from an error code to a list of generators used to create multiple
/// correction producers used to build fixes for those diagnostics. The
/// generators used for lint rules are in the [lintMultiProducerMap].
///
/// The expectation is that only one of the correction producers will produce
/// a change for a given fix. If more than one change is produced the result
/// will almost certainly be invalid code.
static const Map<ErrorCode, List<MultiProducerGenerator>>
nonLintMultiProducerMap = {
CompileTimeErrorCode.EXTENDS_NON_CLASS: [
DataDriven.newInstance,
],
// TODO(brianwilkerson) The following fix fails if an invocation of the
// function is the argument that needs to be removed.
// CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS: [
// DataDriven.newInstance,
// ],
// TODO(brianwilkerson) The following fix fails if an invocation of the
// function is the argument that needs to be updated.
// CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS_COULD_BE_NAMED: [
// DataDriven.newInstance,
// ],
CompileTimeErrorCode.IMPLEMENTS_NON_CLASS: [
DataDriven.newInstance,
],
CompileTimeErrorCode.INVALID_OVERRIDE: [
DataDriven.newInstance,
],
CompileTimeErrorCode.MIXIN_OF_NON_CLASS: [
DataDriven.newInstance,
],
CompileTimeErrorCode.NEW_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT: [
DataDriven.newInstance,
],
CompileTimeErrorCode.NOT_ENOUGH_POSITIONAL_ARGUMENTS: [
DataDriven.newInstance,
],
CompileTimeErrorCode.UNDEFINED_CLASS: [
DataDriven.newInstance,
],
CompileTimeErrorCode.UNDEFINED_FUNCTION: [
DataDriven.newInstance,
],
CompileTimeErrorCode.UNDEFINED_GETTER: [
DataDriven.newInstance,
],
CompileTimeErrorCode.UNDEFINED_IDENTIFIER: [
DataDriven.newInstance,
],
CompileTimeErrorCode.UNDEFINED_METHOD: [
DataDriven.newInstance,
],
CompileTimeErrorCode.UNDEFINED_SETTER: [
DataDriven.newInstance,
],
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS: [
DataDriven.newInstance,
],
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR: [
DataDriven.newInstance,
],
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_EXTENSION: [
DataDriven.newInstance,
],
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_METHOD: [
DataDriven.newInstance,
],
HintCode.DEPRECATED_MEMBER_USE: [
DataDriven.newInstance,
],
HintCode.DEPRECATED_MEMBER_USE_WITH_MESSAGE: [
DataDriven.newInstance,
],
HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD: [
DataDriven.newInstance,
],
};
/// A map from an error code to a generator used to create the correction
/// producer used to build a fix for that diagnostic. The generators used for
/// lint rules are in the [lintProducerMap].
static const Map<ErrorCode, ProducerGenerator> nonLintProducerMap = {};
/// Information about the workspace containing the libraries in which changes
/// will be produced.
final DartChangeWorkspace workspace;
/// The change builder used to build the changes required to fix the
/// diagnostics.
ChangeBuilder builder;
/// Initialize a newly created processor to create fixes for diagnostics in
/// libraries in the [workspace].
BulkFixProcessor(this.workspace) {
builder = ChangeBuilder(workspace: workspace);
}
/// Return a change builder that has been used to create fixes for the
/// diagnostics in the libraries at the given [libraryPaths].
Future<ChangeBuilder> fixErrorsInLibraries(List<String> libraryPaths) async {
for (var path in libraryPaths) {
var session = workspace.getSession(path);
var kind = await session.getSourceKind(path);
if (kind == SourceKind.LIBRARY) {
var libraryResult = await session.getResolvedLibrary(path);
await _fixErrorsInLibrary(libraryResult);
}
}
return builder;
}
/// Use the change [builder] to create fixes for the diagnostics in the
/// library associated with the analysis [result].
Future<void> _fixErrorsInLibrary(ResolvedLibraryResult result) async {
var analysisOptions = result.session.analysisContext.analysisOptions;
for (var unitResult in result.units) {
final fixContext = DartFixContextImpl(
workspace,
unitResult,
null,
(name) => [],
);
for (var error in unitResult.errors) {
var processor = ErrorProcessor.getProcessor(analysisOptions, error);
// Only fix errors not filtered out in analysis options.
if (processor == null || processor.severity != null) {
await _fixSingleError(fixContext, unitResult, error);
}
}
}
}
/// Use the change [builder] and the [fixContext] to create a fix for the
/// given [diagnostic] in the compilation unit associated with the analysis
/// [result].
Future<void> _fixSingleError(DartFixContext fixContext,
ResolvedUnitResult result, AnalysisError diagnostic) async {
var context = CorrectionProducerContext(
dartFixContext: fixContext,
diagnostic: diagnostic,
resolvedResult: result,
selectionOffset: diagnostic.offset,
selectionLength: diagnostic.length,
workspace: workspace,
);
var setupSuccess = context.setupCompute();
if (!setupSuccess) {
return;
}
Future<void> compute(CorrectionProducer producer) async {
producer.configure(context);
await producer.compute(builder);
}
var errorCode = diagnostic.errorCode;
if (errorCode is LintCode) {
var generators = lintProducerMap[errorCode.name];
if (generators != null) {
for (var generator in generators) {
await compute(generator());
}
}
} else {
var generator = nonLintProducerMap[errorCode];
if (generator != null) {
await compute(generator());
}
var multiGenerators = nonLintMultiProducerMap[errorCode];
if (multiGenerators != null) {
for (var multiGenerator in multiGenerators) {
var multiProducer = multiGenerator();
multiProducer.configure(context);
for (var producer in multiProducer.producers) {
await compute(producer);
}
}
}
}
}
}