| // 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:core'; |
| import 'dart:math' as math; |
| |
| import 'package:analysis_server/plugin/edit/fix/fix_core.dart'; |
| import 'package:analysis_server/plugin/edit/fix/fix_dart.dart'; |
| import 'package:analysis_server/src/services/correction/base_processor.dart'; |
| import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_async.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_explicit_cast.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_field_formal_parameters.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_missing_enum_case_clauses.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_missing_required_argument.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_override.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_required.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_required_keyword.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_return_type.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_static.dart'; |
| import 'package:analysis_server/src/services/correction/dart/add_type_annotation.dart'; |
| import 'package:analysis_server/src/services/correction/dart/change_argument_name.dart'; |
| import 'package:analysis_server/src/services/correction/dart/change_to_nearest_precise_value.dart'; |
| import 'package:analysis_server/src/services/correction/dart/change_type_annotation.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_flutter_child.dart'; |
| import 'package:analysis_server/src/services/correction/dart/convert_flutter_children.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_expression_function_body.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_named_arguments.dart'; |
| import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware.dart'; |
| import 'package:analysis_server/src/services/correction/dart/convert_to_on_type.dart'; |
| import 'package:analysis_server/src/services/correction/dart/convert_to_package_import.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/inline_invocation.dart'; |
| import 'package:analysis_server/src/services/correction/dart/inline_typedef.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_dead_if_null.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_if_null_operator.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_question_mark.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/remove_unused.dart'; |
| import 'package:analysis_server/src/services/correction/dart/remove_unused_local_variable.dart'; |
| import 'package:analysis_server/src/services/correction/dart/rename_to_camel_case.dart'; |
| import 'package:analysis_server/src/services/correction/dart/replace_boolean_with_bool.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_new_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_brackets.dart'; |
| import 'package:analysis_server/src/services/correction/dart/replace_with_conditional_assignment.dart'; |
| import 'package:analysis_server/src/services/correction/dart/replace_with_eight_digit_hex.dart'; |
| import 'package:analysis_server/src/services/correction/dart/replace_with_identifier.dart'; |
| import 'package:analysis_server/src/services/correction/dart/replace_with_interpolation.dart'; |
| import 'package:analysis_server/src/services/correction/dart/replace_with_is_empty.dart'; |
| import 'package:analysis_server/src/services/correction/dart/replace_with_null_aware.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/sort_directives.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/dart/wrap_in_future.dart'; |
| import 'package:analysis_server/src/services/correction/dart/wrap_in_text.dart'; |
| import 'package:analysis_server/src/services/correction/fix.dart'; |
| import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart'; |
| import 'package:analysis_server/src/services/correction/levenshtein.dart'; |
| import 'package:analysis_server/src/services/correction/namespace.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:analysis_server/src/utilities/strings.dart'; |
| import 'package:analyzer/dart/analysis/features.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/element/element.dart'; |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/dart/element/type_system.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/src/dart/analysis/session_helper.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/error/inheritance_override.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/java_core.dart'; |
| import 'package:analyzer/src/generated/parser.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/hint/sdk_constraint_extractor.dart'; |
| import 'package:analyzer_plugin/protocol/protocol_common.dart' |
| hide AnalysisError, Element, ElementKind; |
| import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart'; |
| import 'package:analyzer_plugin/src/utilities/string_utilities.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/fixes/fixes.dart' hide FixContributor; |
| import 'package:analyzer_plugin/utilities/range_factory.dart'; |
| import 'package:path/path.dart'; |
| |
| /// A predicate is a one-argument function that returns a boolean value. |
| typedef ElementPredicate = bool Function(Element argument); |
| |
| /// A function that can be executed to create a multi-correction producer. |
| typedef MultiProducerGenerator = MultiCorrectionProducer Function(); |
| |
| /// A function that can be executed to create a correction producer. |
| typedef ProducerGenerator = CorrectionProducer Function(); |
| |
| /// A fix contributor that provides the default set of fixes for Dart files. |
| class DartFixContributor implements FixContributor { |
| @override |
| Future<List<Fix>> computeFixes(DartFixContext context) async { |
| try { |
| var processor = FixProcessor(context); |
| var fixes = await processor.compute(); |
| var fixAllFixes = await _computeFixAllFixes(context, fixes); |
| return List.from(fixes)..addAll(fixAllFixes); |
| } on CancelCorrectionException { |
| return const <Fix>[]; |
| } |
| } |
| |
| Future<List<Fix>> _computeFixAllFixes( |
| DartFixContext context, List<Fix> fixes) async { |
| final analysisError = context.error; |
| final allAnalysisErrors = context.resolveResult.errors.toList(); |
| |
| // Validate inputs: |
| // - return if no fixes |
| // - return if no other analysis errors |
| if (fixes.isEmpty || allAnalysisErrors.length < 2) { |
| return const <Fix>[]; |
| } |
| |
| // Remove any analysis errors that don't have the expected error code name |
| allAnalysisErrors |
| .removeWhere((e) => analysisError.errorCode.name != e.errorCode.name); |
| if (allAnalysisErrors.length < 2) { |
| return const <Fix>[]; |
| } |
| |
| // A map between each FixKind and the List of associated fixes |
| var map = <FixKind, List<Fix>>{}; |
| |
| // Populate the HashMap by looping through all AnalysisErrors, creating a |
| // new FixProcessor to compute the other fixes that can be applied with this |
| // one. |
| // For each fix, put the fix into the HashMap. |
| for (var i = 0; i < allAnalysisErrors.length; i++) { |
| final FixContext fixContextI = DartFixContextImpl( |
| context.workspace, |
| context.resolveResult, |
| allAnalysisErrors[i], |
| (name) => [], |
| ); |
| var processorI = FixProcessor(fixContextI); |
| var fixesListI = await processorI.compute(); |
| for (var f in fixesListI) { |
| if (!map.containsKey(f.kind)) { |
| map[f.kind] = <Fix>[]..add(f); |
| } else { |
| map[f.kind].add(f); |
| } |
| } |
| } |
| |
| // For each FixKind in the HashMap, union each list together, then return |
| // the set of unioned Fixes. |
| var result = <Fix>[]; |
| map.forEach((FixKind kind, List<Fix> fixesListJ) { |
| if (fixesListJ.first.kind.canBeAppliedTogether()) { |
| var unionFix = _unionFixList(fixesListJ); |
| if (unionFix != null) { |
| result.add(unionFix); |
| } |
| } |
| }); |
| return result; |
| } |
| |
| Fix _unionFixList(List<Fix> fixList) { |
| if (fixList == null || fixList.isEmpty) { |
| return null; |
| } else if (fixList.length == 1) { |
| return fixList[0]; |
| } |
| var sourceChange = SourceChange(fixList[0].kind.appliedTogetherMessage); |
| sourceChange.edits = List.from(fixList[0].change.edits); |
| var edits = <SourceEdit>[]; |
| edits.addAll(fixList[0].change.edits[0].edits); |
| sourceChange.linkedEditGroups = |
| List.from(fixList[0].change.linkedEditGroups); |
| for (var i = 1; i < fixList.length; i++) { |
| edits.addAll(fixList[i].change.edits[0].edits); |
| sourceChange.linkedEditGroups..addAll(fixList[i].change.linkedEditGroups); |
| } |
| // Sort the list of SourceEdits so that when the edits are applied, they |
| // are applied from the end of the file to the top of the file. |
| edits.sort((s1, s2) => s2.offset - s1.offset); |
| |
| sourceChange.edits[0].edits = edits; |
| |
| return Fix(fixList[0].kind, sourceChange); |
| } |
| } |
| |
| /// The computer for Dart fixes. |
| class FixProcessor extends BaseProcessor { |
| static const int MAX_LEVENSHTEIN_DISTANCE = 3; |
| |
| /// A map from the names of lint rules to a list of generators used to create |
| /// the correction producers used to build fixes for those diagnostics. The |
| /// generators used for non-lint diagnostics are in the [nonLintProducerMap]. |
| static const Map<String, List<ProducerGenerator>> lintProducerMap = { |
| LintNames.always_declare_return_types: [ |
| AddReturnType.newInstance, |
| ], |
| LintNames.always_require_non_null_named_parameters: [ |
| AddRequired.newInstance, |
| ], |
| LintNames.always_specify_types: [ |
| AddTypeAnnotation.newInstance, |
| ], |
| 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_private_typedef_functions: [ |
| InlineTypedef.newInstance, |
| ], |
| LintNames.avoid_redundant_argument_values: [ |
| RemoveArgument.newInstance, |
| ], |
| LintNames.avoid_relative_lib_imports: [ |
| ConvertToPackageImport.newInstance, |
| ], |
| LintNames.avoid_return_types_on_setters: [ |
| RemoveTypeAnnotation.newInstance, |
| ], |
| LintNames.avoid_returning_null_for_future: [ |
| AddSync.newInstance, |
| WrapInFuture.newInstance, |
| ], |
| LintNames.avoid_types_as_parameter_names: [ |
| ConvertToOnType.newInstance, |
| ], |
| LintNames.avoid_types_on_closure_parameters: [ |
| ReplaceWithIdentifier.newInstance, |
| 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.directives_ordering: [ |
| SortDirectives.newInstance, |
| ], |
| LintNames.empty_catches: [ |
| RemoveEmptyCatch.newInstance, |
| ], |
| LintNames.empty_constructor_bodies: [ |
| RemoveEmptyConstructorBody.newInstance, |
| ], |
| LintNames.empty_statements: [ |
| RemoveEmptyStatement.newInstance, |
| ReplaceWithBrackets.newInstance, |
| ], |
| LintNames.hash_and_equals: [ |
| CreateMethod.newInstance, |
| ], |
| 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, |
| ], |
| 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_expression_function_bodies: [ |
| ConvertToExpressionFunctionBody.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_is_empty: [ |
| ReplaceWithIsEmpty.newInstance, |
| ], |
| LintNames.prefer_is_not_empty: [ |
| UesIsNotEmpty.newInstance, |
| ], |
| LintNames.prefer_if_null_operators: [ |
| ConvertToIfNull.newInstance, |
| ], |
| LintNames.prefer_inlined_adds: [ |
| InlineInvocation.newInstance, |
| ], |
| LintNames.prefer_int_literals: [ |
| ConvertToIntLiteral.newInstance, |
| ], |
| LintNames.prefer_interpolation_to_compose_strings: [ |
| ReplaceWithInterpolation.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_annotate_public_apis: [ |
| AddTypeAnnotation.newInstance, |
| ], |
| LintNames.type_init_formals: [ |
| RemoveTypeAnnotation.newInstance, |
| ], |
| LintNames.unawaited_futures: [ |
| AddAwait.newInstance, |
| ], |
| LintNames.unnecessary_brace_in_string_interps: [ |
| RemoveInterpolationBraces.newInstance, |
| ], |
| LintNames.unnecessary_const: [ |
| RemoveUnnecesaryConst.newInstance, |
| ], |
| LintNames.unnecessary_lambdas: [ |
| ReplaceWithTearOff.newInstance, |
| ], |
| LintNames.unnecessary_new: [ |
| RemoveUnnecessaryNew.newInstance, |
| ], |
| LintNames.unnecessary_null_in_if_null_operators: [ |
| RemoveIfNullOperator.newInstance, |
| ], |
| LintNames.unnecessary_overrides: [ |
| RemoveMethodDeclaration.newInstance, |
| ], |
| LintNames.unnecessary_this: [ |
| RemoveThisExpression.newInstance, |
| ], |
| LintNames.use_full_hex_values_for_flutter_colors: [ |
| ReplaceWithEightDigitHex.newInstance, |
| ], |
| LintNames.use_function_type_syntax_for_parameters: [ |
| ConvertToGenericFunctionSyntax.newInstance, |
| ], |
| LintNames.use_rethrow_when_possible: [ |
| UseRethrow.newInstance, |
| ], |
| }; |
| |
| /// A map from error codes 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]. |
| static const Map<ErrorCode, List<MultiProducerGenerator>> |
| nonLintMultiProducerMap = { |
| CompileTimeErrorCode.UNDEFINED_NAMED_PARAMETER: [ |
| ChangeArgumentName.newInstance |
| ], |
| }; |
| |
| /// A map from error codes to a list of generators used to create the |
| /// correction producers used to build fixes for those diagnostics. The |
| /// generators used for lint rules are in the [lintProducerMap]. |
| static const Map<ErrorCode, List<ProducerGenerator>> nonLintProducerMap = { |
| CompileTimeErrorCode.ASYNC_FOR_IN_WRONG_CONTEXT: [ |
| AddSync.newInstance, |
| ], |
| CompileTimeErrorCode.AWAIT_IN_WRONG_CONTEXT: [ |
| AddSync.newInstance, |
| ], |
| // CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE : [], |
| CompileTimeErrorCode.CONST_INSTANCE_FIELD: [ |
| AddStatic.newInstance, |
| ], |
| CompileTimeErrorCode.CONST_WITH_NON_CONST: [ |
| RemoveConst.newInstance, |
| ], |
| // CompileTimeErrorCode.EXTENSION_OVERRIDE_ACCESS_TO_STATIC_MEMBER : [], |
| // CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS : [], |
| CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS_COULD_BE_NAMED: [ |
| ConvertToNamedArguments.newInstance, |
| ], |
| // CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD : [], |
| CompileTimeErrorCode.INTEGER_LITERAL_IMPRECISE_AS_DOUBLE: [ |
| ChangeToNearestPreciseValue.newInstance, |
| ], |
| // CompileTimeErrorCode.INVALID_ANNOTATION : [], |
| CompileTimeErrorCode.MISSING_DEFAULT_VALUE_FOR_PARAMETER: [ |
| AddRequiredKeyword.newInstance, |
| ], |
| // CompileTimeErrorCode.MIXIN_APPLICATION_NOT_IMPLEMENTED_INTERFACE : [], |
| // CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT : [], |
| // CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT : [], |
| CompileTimeErrorCode.NULLABLE_TYPE_IN_EXTENDS_CLAUSE: [ |
| RemoveQuestionMark.newInstance, |
| ], |
| CompileTimeErrorCode.NULLABLE_TYPE_IN_IMPLEMENTS_CLAUSE: [ |
| RemoveQuestionMark.newInstance, |
| ], |
| CompileTimeErrorCode.NULLABLE_TYPE_IN_ON_CLAUSE: [ |
| RemoveQuestionMark.newInstance, |
| ], |
| CompileTimeErrorCode.NULLABLE_TYPE_IN_WITH_CLAUSE: [ |
| RemoveQuestionMark.newInstance, |
| ], |
| // CompileTimeErrorCode.UNDEFINED_ANNOTATION : [], |
| // CompileTimeErrorCode.UNDEFINED_CLASS : [], |
| // CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT : [], |
| // CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER : [], |
| // CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD : [], |
| // CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER : [], |
| CompileTimeErrorCode.UNDEFINED_NAMED_PARAMETER: [ |
| ConvertFlutterChild.newInstance, |
| ConvertFlutterChildren.newInstance, |
| ], |
| // CompileTimeErrorCode.UNQUALIFIED_REFERENCE_TO_STATIC_MEMBER_OF_EXTENDED_TYPE : [], |
| // CompileTimeErrorCode.URI_DOES_NOT_EXIST : [], |
| |
| HintCode.CAN_BE_NULL_AFTER_NULL_AWARE: [ |
| ReplaceWithNullAware.newInstance, |
| ], |
| // HintCode.DEAD_CODE : [], |
| // HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH : [], |
| // HintCode.DEAD_CODE_ON_CATCH_SUBTYPE : [], |
| // HintCode.DIVISION_OPTIMIZATION : [], |
| // HintCode.DUPLICATE_HIDDEN_NAME : [], |
| // HintCode.DUPLICATE_IMPORT : [], |
| // HintCode.DUPLICATE_SHOWN_NAME : [], |
| // HintCode.INVALID_FACTORY_ANNOTATION : [], |
| // HintCode.INVALID_IMMUTABLE_ANNOTATION : [], |
| // HintCode.INVALID_LITERAL_ANNOTATION : [], |
| // HintCode.INVALID_REQUIRED_NAMED_PARAM : [], |
| // HintCode.INVALID_REQUIRED_OPTIONAL_POSITIONAL_PARAM : [], |
| // HintCode.INVALID_REQUIRED_POSITIONAL_PARAM : [], |
| // HintCode.INVALID_SEALED_ANNOTATION : [], |
| HintCode.MISSING_REQUIRED_PARAM: [ |
| AddMissingRequiredArgument.newInstance, |
| ], |
| HintCode.MISSING_REQUIRED_PARAM_WITH_DETAILS: [ |
| AddMissingRequiredArgument.newInstance, |
| ], |
| HintCode.NULLABLE_TYPE_IN_CATCH_CLAUSE: [ |
| RemoveQuestionMark.newInstance, |
| ], |
| // HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD : [], |
| // HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER : [], |
| // HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD : [], |
| // HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER : [], |
| // HintCode.SDK_VERSION_AS_EXPRESSION_IN_CONST_CONTEXT : [], |
| // HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE : [], |
| // HintCode.SDK_VERSION_BOOL_OPERATOR_IN_CONST_CONTEXT : [], |
| // HintCode.SDK_VERSION_EQ_EQ_OPERATOR_IN_CONST_CONTEXT : [], |
| // HintCode.SDK_VERSION_EXTENSION_METHODS : [], |
| // HintCode.SDK_VERSION_GT_GT_GT_OPERATOR : [], |
| // HintCode.SDK_VERSION_IS_EXPRESSION_IN_CONST_CONTEXT : [], |
| // HintCode.SDK_VERSION_SET_LITERAL : [], |
| // HintCode.SDK_VERSION_UI_AS_CODE : [], |
| // HintCode.TYPE_CHECK_IS_NOT_NULL : [], |
| // HintCode.TYPE_CHECK_IS_NULL : [], |
| // HintCode.UNDEFINED_HIDDEN_NAME : [], |
| // HintCode.UNDEFINED_SHOWN_NAME : [], |
| // HintCode.UNNECESSARY_CAST : [], |
| // HintCode.UNUSED_CATCH_CLAUSE : [], |
| // HintCode.UNUSED_CATCH_STACK : [], |
| HintCode.UNUSED_ELEMENT: [ |
| RemoveUnusedElement.newInstance, |
| ], |
| HintCode.UNUSED_FIELD: [ |
| RemoveUnusedField.newInstance, |
| ], |
| // HintCode.UNUSED_IMPORT : [], |
| // HintCode.UNUSED_LABEL : [], |
| HintCode.UNUSED_LOCAL_VARIABLE: [ |
| RemoveUnusedLocalVariable.newInstance, |
| ], |
| // HintCode.UNUSED_SHOWN_NAME : [], |
| |
| // ParserErrorCode.EXPECTED_TOKEN : [], |
| // ParserErrorCode.GETTER_WITH_PARAMETERS : [], |
| ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE: [ |
| AddTypeAnnotation.newInstance, |
| ], |
| // ParserErrorCode.VAR_AS_TYPE_NAME : [], |
| |
| // StaticTypeWarningCode.ILLEGAL_ASYNC_RETURN_TYPE : [], |
| // StaticTypeWarningCode.INSTANCE_ACCESS_TO_STATIC_MEMBER : [], |
| StaticTypeWarningCode.INVALID_ASSIGNMENT: [ |
| AddExplicitCast.newInstance, |
| ChangeTypeAnnotation.newInstance, |
| ], |
| // StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION : [], |
| // StaticTypeWarningCode.NON_BOOL_CONDITION : [], |
| // StaticTypeWarningCode.NON_TYPE_AS_TYPE_ARGUMENT : [], |
| // StaticTypeWarningCode.UNDEFINED_FUNCTION : [], |
| // StaticTypeWarningCode.UNDEFINED_GETTER : [], |
| // StaticTypeWarningCode.UNDEFINED_METHOD : [], |
| // StaticTypeWarningCode.UNDEFINED_SETTER : [], |
| // StaticTypeWarningCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR : [], |
| // StaticTypeWarningCode.UNQUALIFIED_REFERENCE_TO_NON_LOCAL_STATIC_MEMBER : [], |
| |
| // StaticWarningCode.ASSIGNMENT_TO_FINAL : [], |
| // StaticWarningCode.ASSIGNMENT_TO_FINAL_LOCAL : [], |
| StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE: [ |
| WrapInText.newInstance, |
| ], |
| // StaticWarningCode.CAST_TO_NON_TYPE : [], |
| // StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER : [], |
| StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION: [ |
| RemoveDeadIfNull.newInstance, |
| ], |
| // StaticWarningCode.FINAL_NOT_INITIALIZED : [], |
| StaticWarningCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_1: [ |
| AddFieldFormalParameters.newInstance, |
| ], |
| StaticWarningCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_2: [ |
| AddFieldFormalParameters.newInstance, |
| ], |
| StaticWarningCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_3_PLUS: [ |
| AddFieldFormalParameters.newInstance, |
| ], |
| StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH: [ |
| AddMissingEnumCaseClauses.newInstance, |
| ], |
| // StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR : [], |
| // StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS : [], |
| // StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR : [], |
| // StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE : [], |
| // StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE : [], |
| // StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO : [], |
| // StaticWarningCode.NON_TYPE_IN_CATCH_CLAUSE : [], |
| // StaticWarningCode.NOT_A_TYPE : [], |
| // StaticWarningCode.TYPE_TEST_WITH_UNDEFINED_NAME : [], |
| StaticWarningCode.UNDEFINED_CLASS_BOOLEAN: [ |
| ReplaceBooleanWithBool.newInstance |
| ], |
| // StaticWarningCode.UNDEFINED_IDENTIFIER : [], |
| StaticWarningCode.UNDEFINED_IDENTIFIER_AWAIT: [ |
| AddSync.newInstance, |
| ], |
| }; |
| |
| final DartFixContext context; |
| |
| final ResourceProvider resourceProvider; |
| final TypeSystem typeSystem; |
| |
| final LibraryElement unitLibraryElement; |
| final CompilationUnit unit; |
| final AnalysisError error; |
| |
| final int errorOffset; |
| |
| final int errorLength; |
| |
| final List<Fix> fixes = <Fix>[]; |
| |
| AstNode coveredNode; |
| |
| FixProcessor(this.context) |
| : resourceProvider = context.resolveResult.session.resourceProvider, |
| typeSystem = context.resolveResult.typeSystem, |
| unitLibraryElement = context.resolveResult.libraryElement, |
| unit = context.resolveResult.unit, |
| error = context.error, |
| errorOffset = context.error.offset, |
| errorLength = context.error.length, |
| super( |
| resolvedResult: context.resolveResult, |
| workspace: context.workspace, |
| ); |
| |
| DartType get coreTypeBool => context.resolveResult.typeProvider.boolType; |
| |
| FeatureSet get _featureSet { |
| return unit.featureSet; |
| } |
| |
| bool get _isNonNullable => _featureSet.isEnabled(Feature.non_nullable); |
| |
| Future<List<Fix>> compute() async { |
| node = NodeLocator2(errorOffset).searchWithin(unit); |
| coveredNode = |
| NodeLocator2(errorOffset, math.max(errorOffset + errorLength - 1, 0)) |
| .searchWithin(unit); |
| if (coveredNode == null) { |
| // TODO(brianwilkerson) Figure out why the coveredNode is sometimes null. |
| return fixes; |
| } |
| |
| // analyze ErrorCode |
| var errorCode = error.errorCode; |
| if (errorCode == |
| CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE) { |
| await _addFix_replaceWithConstInstanceCreation(); |
| } |
| if (errorCode == CompileTimeErrorCode.INVALID_ANNOTATION || |
| errorCode == CompileTimeErrorCode.UNDEFINED_ANNOTATION) { |
| if (node is Annotation) { |
| Annotation annotation = node; |
| var name = annotation.name; |
| if (name != null && name.staticElement == null) { |
| node = name; |
| if (annotation.arguments == null) { |
| await _addFix_importLibrary_withTopLevelVariable(); |
| } else { |
| await _addFix_importLibrary_withType(); |
| await _addFix_createClass(); |
| await _addFix_undefinedClass_useSimilar(); |
| } |
| } |
| } |
| } |
| if (errorCode == |
| CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT) { |
| await _addFix_createConstructorSuperExplicit(); |
| } |
| if (errorCode == |
| CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT) { |
| await _addFix_createConstructorSuperImplicit(); |
| // TODO(brianwilkerson) The following was added because fasta produces |
| // NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT in places where analyzer produced |
| // NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT |
| await _addFix_createConstructorSuperExplicit(); |
| } |
| if (errorCode == |
| CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT) { |
| await _addFix_createConstructorSuperExplicit(); |
| } |
| if (errorCode == CompileTimeErrorCode.URI_DOES_NOT_EXIST) { |
| await _addFix_createImportUri(); |
| await _addFix_createPartUri(); |
| } |
| if (errorCode == HintCode.DEAD_CODE) { |
| await _addFix_removeDeadCode(); |
| } |
| if (errorCode == HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH || |
| errorCode == HintCode.DEAD_CODE_ON_CATCH_SUBTYPE) { |
| await _addFix_removeDeadCode(); |
| // TODO(brianwilkerson) Add a fix to move the unreachable catch clause to |
| // a place where it can be reached (when possible). |
| } |
| // TODO(brianwilkerson) Define a syntax for deprecated members to indicate |
| // how to update the code and implement a fix to apply the update. |
| // if (errorCode == HintCode.DEPRECATED_MEMBER_USE || |
| // errorCode == HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE) { |
| // await _addFix_replaceDeprecatedMemberUse(); |
| // } |
| if (errorCode == HintCode.DIVISION_OPTIMIZATION) { |
| await _addFix_useEffectiveIntegerDivision(); |
| } |
| if (errorCode == HintCode.DUPLICATE_IMPORT) { |
| await _addFix_removeUnusedImport(); |
| } |
| if (errorCode == HintCode.DUPLICATE_HIDDEN_NAME || |
| errorCode == HintCode.DUPLICATE_SHOWN_NAME) { |
| await _addFix_removeNameFromCombinator(); |
| } |
| // TODO(brianwilkerson) Add a fix to convert the path to a package: import. |
| // if (errorCode == HintCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE) { |
| // await _addFix_convertPathToPackageUri(); |
| // } |
| if (errorCode == HintCode.INVALID_FACTORY_ANNOTATION || |
| errorCode == HintCode.INVALID_IMMUTABLE_ANNOTATION || |
| errorCode == HintCode.INVALID_LITERAL_ANNOTATION || |
| errorCode == HintCode.INVALID_REQUIRED_NAMED_PARAM || |
| errorCode == HintCode.INVALID_REQUIRED_OPTIONAL_POSITIONAL_PARAM || |
| errorCode == HintCode.INVALID_REQUIRED_POSITIONAL_PARAM || |
| errorCode == HintCode.INVALID_SEALED_ANNOTATION) { |
| await _addFix_removeAnnotation(); |
| } |
| if (errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER || |
| errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD || |
| errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD || |
| errorCode == HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER) { |
| await _addFix_removeAnnotation(); |
| } |
| // TODO(brianwilkerson) Add a fix to normalize the path. |
| // if (errorCode == HintCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT) { |
| // await _addFix_normalizeUri(); |
| // } |
| if (errorCode == HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE) { |
| await _addFix_importAsync(); |
| await _addFix_updateSdkConstraints('2.1.0'); |
| } |
| if (errorCode == HintCode.SDK_VERSION_SET_LITERAL) { |
| await _addFix_updateSdkConstraints('2.2.0'); |
| } |
| if (errorCode == HintCode.SDK_VERSION_AS_EXPRESSION_IN_CONST_CONTEXT || |
| errorCode == HintCode.SDK_VERSION_BOOL_OPERATOR_IN_CONST_CONTEXT || |
| errorCode == HintCode.SDK_VERSION_EQ_EQ_OPERATOR_IN_CONST_CONTEXT || |
| errorCode == HintCode.SDK_VERSION_GT_GT_GT_OPERATOR || |
| errorCode == HintCode.SDK_VERSION_IS_EXPRESSION_IN_CONST_CONTEXT || |
| errorCode == HintCode.SDK_VERSION_UI_AS_CODE) { |
| await _addFix_updateSdkConstraints('2.2.2'); |
| } |
| if (errorCode == HintCode.SDK_VERSION_EXTENSION_METHODS) { |
| await _addFix_updateSdkConstraints('2.6.0'); |
| } |
| if (errorCode == HintCode.TYPE_CHECK_IS_NOT_NULL) { |
| await _addFix_isNotNull(); |
| } |
| if (errorCode == HintCode.TYPE_CHECK_IS_NULL) { |
| await _addFix_isNull(); |
| } |
| if (errorCode == HintCode.UNDEFINED_HIDDEN_NAME || |
| errorCode == HintCode.UNDEFINED_SHOWN_NAME) { |
| await _addFix_removeNameFromCombinator(); |
| } |
| if (errorCode == HintCode.UNNECESSARY_CAST) { |
| await _addFix_removeUnnecessaryCast(); |
| } |
| // TODO(brianwilkerson) Add a fix to remove the method. |
| // if (errorCode == HintCode.UNNECESSARY_NO_SUCH_METHOD) { |
| // await _addFix_removeMethodDeclaration(); |
| // } |
| // TODO(brianwilkerson) Add a fix to remove the type check. |
| // if (errorCode == HintCode.UNNECESSARY_TYPE_CHECK_FALSE || |
| // errorCode == HintCode.UNNECESSARY_TYPE_CHECK_TRUE) { |
| // await _addFix_removeUnnecessaryTypeCheck(); |
| // } |
| if (errorCode == HintCode.UNUSED_CATCH_CLAUSE) { |
| await _addFix_removeUnusedCatchClause(); |
| } |
| if (errorCode == HintCode.UNUSED_CATCH_STACK) { |
| await _addFix_removeUnusedCatchStack(); |
| } |
| if (errorCode == HintCode.UNUSED_IMPORT) { |
| await _addFix_removeUnusedImport(); |
| } |
| if (errorCode == HintCode.UNUSED_LABEL) { |
| await _addFix_removeUnusedLabel(); |
| } |
| if (errorCode == HintCode.UNUSED_SHOWN_NAME) { |
| await _addFix_removeNameFromCombinator(); |
| } |
| if (errorCode == ParserErrorCode.EXPECTED_TOKEN) { |
| await _addFix_insertSemicolon(); |
| } |
| if (errorCode == ParserErrorCode.GETTER_WITH_PARAMETERS) { |
| await _addFix_removeParameters_inGetterDeclaration(); |
| } |
| if (errorCode == ParserErrorCode.VAR_AS_TYPE_NAME) { |
| await _addFix_replaceVarWithDynamic(); |
| } |
| if (errorCode == StaticWarningCode.ASSIGNMENT_TO_FINAL) { |
| await _addFix_makeFieldNotFinal(); |
| } |
| if (errorCode == StaticWarningCode.ASSIGNMENT_TO_FINAL_LOCAL) { |
| await _addFix_makeVariableNotFinal(); |
| } |
| if (errorCode == StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER) { |
| await _addFix_makeEnclosingClassAbstract(); |
| } |
| if (errorCode == CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS || |
| errorCode == |
| CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS_COULD_BE_NAMED) { |
| await _addFix_createConstructor_insteadOfSyntheticDefault(); |
| await _addFix_addMissingParameter(); |
| } |
| if (errorCode == StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR) { |
| await _addFix_createConstructor_named(); |
| } |
| if (errorCode == StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER || |
| errorCode == |
| StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE || |
| errorCode == |
| StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO || |
| errorCode == |
| StaticWarningCode |
| .NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE || |
| errorCode == |
| StaticWarningCode |
| .NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR || |
| errorCode == |
| StaticWarningCode |
| .NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS) { |
| // make class abstract |
| await _addFix_makeEnclosingClassAbstract(); |
| await _addFix_createNoSuchMethod(); |
| // implement methods |
| await _addFix_createMissingOverrides(); |
| } |
| if (errorCode == CompileTimeErrorCode.UNDEFINED_CLASS || |
| errorCode == StaticWarningCode.CAST_TO_NON_TYPE || |
| errorCode == StaticWarningCode.NOT_A_TYPE || |
| errorCode == StaticWarningCode.TYPE_TEST_WITH_UNDEFINED_NAME) { |
| await _addFix_importLibrary_withType(); |
| await _addFix_createClass(); |
| await _addFix_createMixin(); |
| await _addFix_undefinedClass_useSimilar(); |
| } |
| if (errorCode == StaticWarningCode.NON_TYPE_IN_CATCH_CLAUSE) { |
| await _addFix_importLibrary_withType(); |
| } |
| if (errorCode == StaticWarningCode.FINAL_NOT_INITIALIZED) { |
| await _addFix_createConstructor_forUninitializedFinalFields(); |
| } |
| if (errorCode == StaticWarningCode.UNDEFINED_IDENTIFIER) { |
| await _addFix_undefinedClassAccessor_useSimilar(); |
| await _addFix_createClass(); |
| await _addFix_createField(); |
| await _addFix_createGetter(); |
| await _addFix_createFunction_forFunctionType(); |
| await _addFix_createMixin(); |
| await _addFix_createSetter(); |
| await _addFix_importLibrary_withType(); |
| await _addFix_importLibrary_withExtension(); |
| await _addFix_importLibrary_withFunction(); |
| await _addFix_importLibrary_withTopLevelVariable(); |
| await _addFix_createLocalVariable(); |
| } |
| if (errorCode == CompileTimeErrorCode.UNDEFINED_NAMED_PARAMETER) { |
| await _addFix_addMissingParameterNamed(); |
| } |
| if (errorCode == StaticTypeWarningCode.ILLEGAL_ASYNC_RETURN_TYPE) { |
| await _addFix_illegalAsyncReturnType(); |
| } |
| if (errorCode == StaticTypeWarningCode.INSTANCE_ACCESS_TO_STATIC_MEMBER) { |
| await _addFix_useStaticAccess_method(); |
| await _addFix_useStaticAccess_property(); |
| } |
| if (errorCode == |
| StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION) { |
| await _addFix_removeParentheses_inGetterInvocation(); |
| } |
| if (errorCode == StaticTypeWarningCode.NON_BOOL_CONDITION) { |
| await _addFix_nonBoolCondition_addNotNull(); |
| } |
| if (errorCode == StaticTypeWarningCode.NON_TYPE_AS_TYPE_ARGUMENT) { |
| await _addFix_importLibrary_withType(); |
| await _addFix_createClass(); |
| await _addFix_createMixin(); |
| } |
| if (errorCode == StaticTypeWarningCode.UNDEFINED_FUNCTION) { |
| await _addFix_createClass(); |
| await _addFix_importLibrary_withExtension(); |
| await _addFix_importLibrary_withFunction(); |
| await _addFix_importLibrary_withType(); |
| await _addFix_undefinedFunction_useSimilar(); |
| await _addFix_undefinedFunction_create(); |
| } |
| if (errorCode == StaticTypeWarningCode.UNDEFINED_GETTER) { |
| await _addFix_undefinedClassAccessor_useSimilar(); |
| await _addFix_createField(); |
| await _addFix_createGetter(); |
| await _addFix_createFunction_forFunctionType(); |
| // TODO(brianwilkerson) The following were added because fasta produces |
| // UNDEFINED_GETTER in places where analyzer produced UNDEFINED_IDENTIFIER |
| await _addFix_createClass(); |
| await _addFix_createMixin(); |
| await _addFix_createLocalVariable(); |
| await _addFix_importLibrary_withTopLevelVariable(); |
| await _addFix_importLibrary_withType(); |
| } |
| if (errorCode == CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER) { |
| await _addFix_undefinedClassAccessor_useSimilar(); |
| await _addFix_createGetter(); |
| } |
| if (errorCode == StaticTypeWarningCode.UNDEFINED_METHOD) { |
| await _addFix_createClass(); |
| await _addFix_importLibrary_withFunction(); |
| await _addFix_importLibrary_withType(); |
| await _addFix_undefinedMethod_useSimilar(); |
| await _addFix_createMethod(); |
| await _addFix_undefinedFunction_create(); |
| } |
| if (errorCode == CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD) { |
| await _addFix_undefinedMethod_useSimilar(); |
| await _addFix_createMethod(); |
| } |
| if (errorCode == StaticTypeWarningCode.UNDEFINED_SETTER) { |
| await _addFix_undefinedClassAccessor_useSimilar(); |
| await _addFix_createField(); |
| await _addFix_createSetter(); |
| } |
| if (errorCode == CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER) { |
| await _addFix_undefinedClassAccessor_useSimilar(); |
| await _addFix_createSetter(); |
| } |
| if (errorCode == |
| CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD) { |
| await _addFix_createField_initializingFormal(); |
| } |
| if (errorCode == |
| StaticTypeWarningCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR) { |
| await _addFix_moveTypeArgumentsToClass(); |
| await _addFix_removeTypeArguments(); |
| } |
| if (errorCode == |
| CompileTimeErrorCode.MIXIN_APPLICATION_NOT_IMPLEMENTED_INTERFACE) { |
| await _addFix_extendClassForMixin(); |
| } |
| if (errorCode == |
| CompileTimeErrorCode.EXTENSION_OVERRIDE_ACCESS_TO_STATIC_MEMBER) { |
| await _addFix_replaceWithExtensionName(); |
| } |
| if (errorCode == |
| CompileTimeErrorCode |
| .UNQUALIFIED_REFERENCE_TO_STATIC_MEMBER_OF_EXTENDED_TYPE) { |
| await _addFix_qualifyReference(); |
| // TODO(brianwilkerson) Consider adding fixes to create a field, getter, |
| // method or setter. The existing _addFix methods would need to be |
| // updated so that only the appropriate subset is generated. |
| } |
| if (errorCode == |
| StaticTypeWarningCode |
| .UNQUALIFIED_REFERENCE_TO_NON_LOCAL_STATIC_MEMBER) { |
| await _addFix_qualifyReference(); |
| // TODO(brianwilkerson) Consider adding fixes to create a field, getter, |
| // method or setter. The existing _addFix methods would need to be |
| // updated so that only the appropriate subset is generated. |
| } |
| await _addFromProducers(); |
| |
| // done |
| return fixes; |
| } |
| |
| Future<Fix> computeFix() async { |
| var fixes = await compute(); |
| fixes.sort(Fix.SORT_BY_RELEVANCE); |
| return fixes.isNotEmpty ? fixes.first : null; |
| } |
| |
| Future<void> _addFix_addMissingParameter() async { |
| // The error is reported on ArgumentList. |
| if (node is! ArgumentList) { |
| return; |
| } |
| ArgumentList argumentList = node; |
| List<Expression> arguments = argumentList.arguments; |
| |
| // Prepare the invoked element. |
| var context = _ExecutableParameters(sessionHelper, node.parent); |
| if (context == null) { |
| return; |
| } |
| |
| // prepare the argument to add a new parameter for |
| var numRequired = context.required.length; |
| if (numRequired >= arguments.length) { |
| return; |
| } |
| var argument = arguments[numRequired]; |
| |
| Future<void> addParameter( |
| FixKind kind, int offset, String prefix, String suffix) async { |
| if (offset != null) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(context.file, (builder) { |
| builder.addInsertion(offset, (builder) { |
| builder.write(prefix); |
| builder.writeParameterMatchingArgument( |
| argument, numRequired, <String>{}); |
| builder.write(suffix); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, kind); |
| } |
| } |
| |
| // Suggest adding a required parameter. |
| { |
| var kind = DartFixKind.ADD_MISSING_PARAMETER_REQUIRED; |
| if (context.required.isNotEmpty) { |
| var prevNode = await context.getParameterNode(context.required.last); |
| await addParameter(kind, prevNode?.end, ', ', ''); |
| } else { |
| var parameterList = await context.getParameterList(); |
| var offset = parameterList?.leftParenthesis?.end; |
| var suffix = context.executable.parameters.isNotEmpty ? ', ' : ''; |
| await addParameter(kind, offset, '', suffix); |
| } |
| } |
| |
| // Suggest adding the first optional positional parameter. |
| if (context.optionalPositional.isEmpty && context.named.isEmpty) { |
| var kind = DartFixKind.ADD_MISSING_PARAMETER_POSITIONAL; |
| var prefix = context.required.isNotEmpty ? ', [' : '['; |
| if (context.required.isNotEmpty) { |
| var prevNode = await context.getParameterNode(context.required.last); |
| await addParameter(kind, prevNode?.end, prefix, ']'); |
| } else { |
| var parameterList = await context.getParameterList(); |
| var offset = parameterList?.leftParenthesis?.end; |
| await addParameter(kind, offset, prefix, ']'); |
| } |
| } |
| } |
| |
| Future<void> _addFix_addMissingParameterNamed() async { |
| // Prepare the name of the missing parameter. |
| if (this.node is! SimpleIdentifier) { |
| return; |
| } |
| SimpleIdentifier node = this.node; |
| var name = node.name; |
| |
| // We expect that the node is part of a NamedExpression. |
| if (node.parent?.parent is! NamedExpression) { |
| return; |
| } |
| NamedExpression namedExpression = node.parent.parent; |
| |
| // We should be in an ArgumentList. |
| if (namedExpression.parent is! ArgumentList) { |
| return; |
| } |
| var argumentList = namedExpression.parent; |
| |
| // Prepare the invoked element. |
| var context = _ExecutableParameters(sessionHelper, argumentList.parent); |
| if (context == null) { |
| return; |
| } |
| |
| // We cannot add named parameters when there are positional positional. |
| if (context.optionalPositional.isNotEmpty) { |
| return; |
| } |
| |
| Future<void> addParameter(int offset, String prefix, String suffix) async { |
| if (offset != null) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(context.file, (builder) { |
| builder.addInsertion(offset, (builder) { |
| builder.write(prefix); |
| builder |
| .writeParameterMatchingArgument(namedExpression, 0, <String>{}); |
| builder.write(suffix); |
| }); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.ADD_MISSING_PARAMETER_NAMED, |
| args: [name]); |
| } |
| } |
| |
| if (context.named.isNotEmpty) { |
| var prevNode = await context.getParameterNode(context.named.last); |
| await addParameter(prevNode?.end, ', ', ''); |
| } else if (context.required.isNotEmpty) { |
| var prevNode = await context.getParameterNode(context.required.last); |
| await addParameter(prevNode?.end, ', {', '}'); |
| } else { |
| var parameterList = await context.getParameterList(); |
| await addParameter(parameterList?.leftParenthesis?.end, '{', '}'); |
| } |
| } |
| |
| Future<void> _addFix_createClass() async { |
| Element prefixElement; |
| String name; |
| SimpleIdentifier nameNode; |
| if (node is SimpleIdentifier) { |
| var parent = node.parent; |
| if (parent is PrefixedIdentifier) { |
| PrefixedIdentifier prefixedIdentifier = parent; |
| prefixElement = prefixedIdentifier.prefix.staticElement; |
| if (prefixElement == null) { |
| return; |
| } |
| parent = prefixedIdentifier.parent; |
| nameNode = prefixedIdentifier.identifier; |
| name = prefixedIdentifier.identifier.name; |
| } else { |
| nameNode = node; |
| name = nameNode.name; |
| } |
| if (!_mayBeTypeIdentifier(nameNode)) { |
| return; |
| } |
| } else { |
| return; |
| } |
| // prepare environment |
| Element targetUnit; |
| var prefix = ''; |
| var suffix = ''; |
| var offset = -1; |
| String filePath; |
| if (prefixElement == null) { |
| targetUnit = unit.declaredElement; |
| var enclosingMember = node.thisOrAncestorMatching((node) => |
| node is CompilationUnitMember && node.parent is CompilationUnit); |
| if (enclosingMember == null) { |
| return; |
| } |
| offset = enclosingMember.end; |
| filePath = file; |
| prefix = '$eol$eol'; |
| } else { |
| for (var import in unitLibraryElement.imports) { |
| if (prefixElement is PrefixElement && import.prefix == prefixElement) { |
| var library = import.importedLibrary; |
| if (library != null) { |
| targetUnit = library.definingCompilationUnit; |
| var targetSource = targetUnit.source; |
| try { |
| offset = targetSource.contents.data.length; |
| filePath = targetSource.fullName; |
| prefix = '$eol'; |
| suffix = '$eol'; |
| } on FileSystemException { |
| // If we can't read the file to get the offset, then we can't |
| // create a fix. |
| } |
| break; |
| } |
| } |
| } |
| } |
| if (offset < 0) { |
| return; |
| } |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(filePath, (DartFileEditBuilder builder) { |
| builder.addInsertion(offset, (DartEditBuilder builder) { |
| builder.write(prefix); |
| builder.writeClassDeclaration(name, nameGroupName: 'NAME'); |
| builder.write(suffix); |
| }); |
| if (prefixElement == null) { |
| builder.addLinkedPosition(range.node(node), 'NAME'); |
| } |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_CLASS, args: [name]); |
| } |
| |
| /// Here we handle cases when there are no constructors in a class, and the |
| /// class has uninitialized final fields. |
| Future<void> _addFix_createConstructor_forUninitializedFinalFields() async { |
| if (node is! SimpleIdentifier || node.parent is! VariableDeclaration) { |
| return; |
| } |
| |
| var classDeclaration = node.thisOrAncestorOfType<ClassDeclaration>(); |
| if (classDeclaration == null) { |
| return; |
| } |
| var className = classDeclaration.name.name; |
| var superType = classDeclaration.declaredElement.supertype; |
| |
| // prepare names of uninitialized final fields |
| var fieldNames = <String>[]; |
| for (var member in classDeclaration.members) { |
| if (member is FieldDeclaration) { |
| var variableList = member.fields; |
| if (variableList.isFinal) { |
| fieldNames.addAll(variableList.variables |
| .where((v) => v.initializer == null) |
| .map((v) => v.name.name)); |
| } |
| } |
| } |
| // prepare location for a new constructor |
| var targetLocation = utils.prepareNewConstructorLocation(classDeclaration); |
| |
| var changeBuilder = _newDartChangeBuilder(); |
| if (flutter.isExactlyStatelessWidgetType(superType) || |
| flutter.isExactlyStatefulWidgetType(superType)) { |
| // Specialize for Flutter widgets. |
| var keyClass = await sessionHelper.getClass(flutter.widgetsUri, 'Key'); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| builder.write(targetLocation.prefix); |
| builder.write('const '); |
| builder.write(className); |
| builder.write('({'); |
| builder.writeType( |
| keyClass.instantiate( |
| typeArguments: const [], |
| nullabilitySuffix: _isNonNullable |
| ? NullabilitySuffix.question |
| : NullabilitySuffix.star, |
| ), |
| ); |
| builder.write(' key'); |
| |
| var childrenFields = <String>[]; |
| for (var fieldName in fieldNames) { |
| if (fieldName == 'child' || fieldName == 'children') { |
| childrenFields.add(fieldName); |
| continue; |
| } |
| builder.write(', this.'); |
| builder.write(fieldName); |
| } |
| for (var fieldName in childrenFields) { |
| builder.write(', this.'); |
| builder.write(fieldName); |
| } |
| |
| builder.write('}) : super(key: key);'); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| } else { |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| builder.write(targetLocation.prefix); |
| builder.writeConstructorDeclaration(className, |
| fieldNames: fieldNames); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| } |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS); |
| } |
| |
| Future<void> _addFix_createConstructor_insteadOfSyntheticDefault() async { |
| if (node is! ArgumentList) { |
| return; |
| } |
| if (node.parent is! InstanceCreationExpression) { |
| return; |
| } |
| InstanceCreationExpression instanceCreation = node.parent; |
| var constructorName = instanceCreation.constructorName; |
| // should be synthetic default constructor |
| var constructorElement = constructorName.staticElement; |
| if (constructorElement == null || |
| !constructorElement.isDefaultConstructor || |
| !constructorElement.isSynthetic) { |
| return; |
| } |
| // prepare target |
| if (constructorElement.enclosingElement is! ClassElement) { |
| return; |
| } |
| |
| // prepare target ClassDeclaration |
| var targetElement = constructorElement.enclosingElement; |
| var targetResult = await sessionHelper.getElementDeclaration(targetElement); |
| if (targetResult.node is! ClassOrMixinDeclaration) { |
| return; |
| } |
| ClassOrMixinDeclaration targetNode = targetResult.node; |
| |
| // prepare location |
| var targetLocation = CorrectionUtils(targetResult.resolvedUnit) |
| .prepareNewConstructorLocation(targetNode); |
| |
| var targetSource = targetElement.source; |
| var targetFile = targetSource.fullName; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(targetFile, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| builder.write(targetLocation.prefix); |
| builder.writeConstructorDeclaration(targetElement.name, |
| argumentList: instanceCreation.argumentList); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_CONSTRUCTOR, |
| args: [constructorName]); |
| } |
| |
| Future<void> _addFix_createConstructor_named() async { |
| SimpleIdentifier name; |
| ConstructorName constructorName; |
| InstanceCreationExpression instanceCreation; |
| if (node is SimpleIdentifier) { |
| // name |
| name = node as SimpleIdentifier; |
| if (name.parent is ConstructorName) { |
| constructorName = name.parent as ConstructorName; |
| if (constructorName.name == name) { |
| // Type.name |
| if (constructorName.parent is InstanceCreationExpression) { |
| instanceCreation = |
| constructorName.parent as InstanceCreationExpression; |
| // new Type.name() |
| if (instanceCreation.constructorName != constructorName) { |
| return; |
| } |
| } |
| } |
| } |
| } |
| // do we have enough information? |
| if (instanceCreation == null) { |
| return; |
| } |
| // prepare target interface type |
| var targetType = constructorName.type.type; |
| if (targetType is! InterfaceType) { |
| return; |
| } |
| |
| // prepare target ClassDeclaration |
| ClassElement targetElement = targetType.element; |
| var targetResult = await sessionHelper.getElementDeclaration(targetElement); |
| if (targetResult.node is! ClassOrMixinDeclaration) { |
| return; |
| } |
| ClassOrMixinDeclaration targetNode = targetResult.node; |
| |
| // prepare location |
| var targetLocation = CorrectionUtils(targetResult.resolvedUnit) |
| .prepareNewConstructorLocation(targetNode); |
| |
| var targetFile = targetElement.source.fullName; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(targetFile, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| builder.write(targetLocation.prefix); |
| builder.writeConstructorDeclaration(targetElement.name, |
| argumentList: instanceCreation.argumentList, |
| constructorName: name, |
| constructorNameGroupName: 'NAME'); |
| builder.write(targetLocation.suffix); |
| }); |
| if (targetFile == file) { |
| builder.addLinkedPosition(range.node(name), 'NAME'); |
| } |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_CONSTRUCTOR, |
| args: [constructorName]); |
| } |
| |
| Future<void> _addFix_createConstructorSuperExplicit() async { |
| if (node.parent is! ConstructorDeclaration || |
| node.parent.parent is! ClassDeclaration) { |
| return; |
| } |
| var targetConstructor = node.parent as ConstructorDeclaration; |
| var targetClassNode = targetConstructor.parent as ClassDeclaration; |
| var targetClassElement = targetClassNode.declaredElement; |
| var superType = targetClassElement.supertype; |
| // add proposals for all super constructors |
| for (var superConstructor in superType.constructors) { |
| var constructorName = superConstructor.name; |
| // skip private |
| if (Identifier.isPrivateName(constructorName)) { |
| continue; |
| } |
| List<ConstructorInitializer> initializers = |
| targetConstructor.initializers; |
| int insertOffset; |
| String prefix; |
| if (initializers.isEmpty) { |
| insertOffset = targetConstructor.parameters.end; |
| prefix = ' : '; |
| } else { |
| var lastInitializer = initializers[initializers.length - 1]; |
| insertOffset = lastInitializer.end; |
| prefix = ', '; |
| } |
| var proposalName = _getConstructorProposalName(superConstructor); |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(insertOffset, (DartEditBuilder builder) { |
| builder.write(prefix); |
| // add super constructor name |
| builder.write('super'); |
| if (!isEmpty(constructorName)) { |
| builder.write('.'); |
| builder.addSimpleLinkedEdit('NAME', constructorName); |
| } |
| // add arguments |
| builder.write('('); |
| var firstParameter = true; |
| for (var parameter in superConstructor.parameters) { |
| // skip non-required parameters |
| if (parameter.isOptional) { |
| break; |
| } |
| // comma |
| if (firstParameter) { |
| firstParameter = false; |
| } else { |
| builder.write(', '); |
| } |
| // default value |
| builder.addSimpleLinkedEdit( |
| parameter.name, getDefaultValueCode(parameter.type)); |
| } |
| builder.write(')'); |
| }); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.ADD_SUPER_CONSTRUCTOR_INVOCATION, |
| args: [proposalName]); |
| } |
| } |
| |
| Future<void> _addFix_createConstructorSuperImplicit() async { |
| var targetClassNode = node.thisOrAncestorOfType<ClassDeclaration>(); |
| var targetClassElement = targetClassNode.declaredElement; |
| var superType = targetClassElement.supertype; |
| var targetClassName = targetClassElement.name; |
| // add proposals for all super constructors |
| for (var superConstructor in superType.constructors) { |
| var constructorName = superConstructor.name; |
| // skip private |
| if (Identifier.isPrivateName(constructorName)) { |
| continue; |
| } |
| // prepare parameters and arguments |
| var requiredParameters = superConstructor.parameters |
| .where((parameter) => parameter.isRequiredPositional); |
| // add proposal |
| var targetLocation = utils.prepareNewConstructorLocation(targetClassNode); |
| var proposalName = _getConstructorProposalName(superConstructor); |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| void writeParameters(bool includeType) { |
| var firstParameter = true; |
| for (var parameter in requiredParameters) { |
| if (firstParameter) { |
| firstParameter = false; |
| } else { |
| builder.write(', '); |
| } |
| var parameterName = parameter.displayName; |
| if (parameterName.length > 1 && parameterName.startsWith('_')) { |
| parameterName = parameterName.substring(1); |
| } |
| if (includeType && builder.writeType(parameter.type)) { |
| builder.write(' '); |
| } |
| builder.write(parameterName); |
| } |
| } |
| |
| builder.write(targetLocation.prefix); |
| builder.write(targetClassName); |
| if (constructorName.isNotEmpty) { |
| builder.write('.'); |
| builder.addSimpleLinkedEdit('NAME', constructorName); |
| } |
| builder.write('('); |
| writeParameters(true); |
| builder.write(') : super'); |
| if (constructorName.isNotEmpty) { |
| builder.write('.'); |
| builder.addSimpleLinkedEdit('NAME', constructorName); |
| } |
| builder.write('('); |
| writeParameters(false); |
| builder.write(');'); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_CONSTRUCTOR_SUPER, |
| args: [proposalName]); |
| } |
| } |
| |
| Future<void> _addFix_createField() async { |
| if (node is! SimpleIdentifier) { |
| return; |
| } |
| SimpleIdentifier nameNode = node; |
| var name = nameNode.name; |
| // prepare target Expression |
| Expression target; |
| { |
| var nameParent = nameNode.parent; |
| if (nameParent is PrefixedIdentifier) { |
| target = nameParent.prefix; |
| } else if (nameParent is PropertyAccess) { |
| target = nameParent.realTarget; |
| } |
| } |
| // prepare target ClassElement |
| var staticModifier = false; |
| ClassElement targetClassElement; |
| if (target != null) { |
| targetClassElement = _getTargetClassElement(target); |
| // maybe static |
| if (target is Identifier) { |
| var targetIdentifier = target; |
| var targetElement = targetIdentifier.staticElement; |
| if (targetElement == null) { |
| return; |
| } |
| staticModifier = targetElement.kind == ElementKind.CLASS; |
| } |
| } else { |
| targetClassElement = getEnclosingClassElement(node); |
| staticModifier = _inStaticContext(); |
| } |
| if (targetClassElement == null) { |
| return; |
| } |
| if (targetClassElement.librarySource.isInSystemLibrary) { |
| return; |
| } |
| utils.targetClassElement = targetClassElement; |
| // prepare target ClassDeclaration |
| var targetDeclarationResult = |
| await sessionHelper.getElementDeclaration(targetClassElement); |
| if (targetDeclarationResult == null) { |
| return; |
| } |
| if (targetDeclarationResult.node is! ClassOrMixinDeclaration) { |
| return; |
| } |
| ClassOrMixinDeclaration targetNode = targetDeclarationResult.node; |
| // prepare location |
| var targetLocation = CorrectionUtils(targetDeclarationResult.resolvedUnit) |
| .prepareNewFieldLocation(targetNode); |
| // build field source |
| var targetSource = targetClassElement.source; |
| var targetFile = targetSource.fullName; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(targetFile, (DartFileEditBuilder builder) { |
| var fieldTypeNode = climbPropertyAccess(nameNode); |
| var fieldType = _inferUndefinedExpressionType(fieldTypeNode); |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| builder.write(targetLocation.prefix); |
| builder.writeFieldDeclaration(name, |
| isStatic: staticModifier, |
| nameGroupName: 'NAME', |
| type: fieldType, |
| typeGroupName: 'TYPE'); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_FIELD, args: [name]); |
| } |
| |
| Future<void> _addFix_createField_initializingFormal() async { |
| // |
| // Ensure that we are in an initializing formal parameter. |
| // |
| var parameter = node.thisOrAncestorOfType<FieldFormalParameter>(); |
| if (parameter == null) { |
| return; |
| } |
| var targetClassNode = parameter.thisOrAncestorOfType<ClassDeclaration>(); |
| if (targetClassNode == null) { |
| return; |
| } |
| var nameNode = parameter.identifier; |
| var name = nameNode.name; |
| var targetLocation = utils.prepareNewFieldLocation(targetClassNode); |
| // |
| // Add proposal. |
| // |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| var fieldType = parameter.type?.type; |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| builder.write(targetLocation.prefix); |
| builder.writeFieldDeclaration(name, |
| nameGroupName: 'NAME', type: fieldType, typeGroupName: 'TYPE'); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_FIELD, args: [name]); |
| } |
| |
| Future<void> _addFix_createFunction_forFunctionType() async { |
| if (node is SimpleIdentifier) { |
| var nameNode = node as SimpleIdentifier; |
| // prepare argument expression (to get parameter) |
| ClassElement targetElement; |
| Expression argument; |
| { |
| var target = getQualifiedPropertyTarget(node); |
| if (target != null) { |
| var targetType = target.staticType; |
| if (targetType != null && targetType.element is ClassElement) { |
| targetElement = targetType.element as ClassElement; |
| argument = target.parent as Expression; |
| } else { |
| return; |
| } |
| } else { |
| var enclosingClass = |
| node.thisOrAncestorOfType<ClassOrMixinDeclaration>(); |
| targetElement = enclosingClass?.declaredElement; |
| argument = nameNode; |
| } |
| } |
| argument = stepUpNamedExpression(argument); |
| // should be argument of some invocation |
| var parameterElement = argument.staticParameterElement; |
| if (parameterElement == null) { |
| return; |
| } |
| // should be parameter of function type |
| var parameterType = parameterElement.type; |
| if (parameterType is InterfaceType && parameterType.isDartCoreFunction) { |
| parameterType = FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: const [], |
| returnType: typeProvider.dynamicType, |
| nullabilitySuffix: NullabilitySuffix.none, |
| ); |
| } |
| if (parameterType is! FunctionType) { |
| return; |
| } |
| var functionType = parameterType as FunctionType; |
| // add proposal |
| if (targetElement != null) { |
| await _addProposal_createFunction_method(targetElement, functionType); |
| } else { |
| await _addProposal_createFunction_function(functionType); |
| } |
| } |
| } |
| |
| Future<void> _addFix_createGetter() async { |
| if (node is! SimpleIdentifier) { |
| return; |
| } |
| SimpleIdentifier nameNode = node; |
| var name = nameNode.name; |
| if (!nameNode.inGetterContext()) { |
| return; |
| } |
| // prepare target |
| Expression target; |
| { |
| var nameParent = nameNode.parent; |
| if (nameParent is PrefixedIdentifier) { |
| target = nameParent.prefix; |
| } else if (nameParent is PropertyAccess) { |
| target = nameParent.realTarget; |
| } |
| } |
| // prepare target element |
| var staticModifier = false; |
| Element targetElement; |
| if (target is ExtensionOverride) { |
| targetElement = target.staticElement; |
| } else if (target is Identifier && |
| target.staticElement is ExtensionElement) { |
| targetElement = target.staticElement; |
| staticModifier = true; |
| } else if (target != null) { |
| // prepare target interface type |
| var targetType = target.staticType; |
| if (targetType is! InterfaceType) { |
| return; |
| } |
| targetElement = targetType.element; |
| // maybe static |
| if (target is Identifier) { |
| var targetIdentifier = target; |
| var targetElement = targetIdentifier.staticElement; |
| staticModifier = targetElement?.kind == ElementKind.CLASS; |
| } |
| } else { |
| targetElement = |
| getEnclosingClassElement(node) ?? getEnclosingExtensionElement(node); |
| if (targetElement == null) { |
| return; |
| } |
| staticModifier = _inStaticContext(); |
| } |
| if (targetElement.librarySource.isInSystemLibrary) { |
| return; |
| } |
| // prepare target declaration |
| var targetDeclarationResult = |
| await sessionHelper.getElementDeclaration(targetElement); |
| if (targetDeclarationResult == null) { |
| return; |
| } |
| if (targetDeclarationResult.node is! ClassOrMixinDeclaration && |
| targetDeclarationResult.node is! ExtensionDeclaration) { |
| return; |
| } |
| CompilationUnitMember targetNode = targetDeclarationResult.node; |
| // prepare location |
| var targetLocation = CorrectionUtils(targetDeclarationResult.resolvedUnit) |
| .prepareNewGetterLocation(targetNode); |
| // build method source |
| var targetSource = targetElement.source; |
| var targetFile = targetSource.fullName; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(targetFile, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| var fieldTypeNode = climbPropertyAccess(nameNode); |
| var fieldType = _inferUndefinedExpressionType(fieldTypeNode); |
| builder.write(targetLocation.prefix); |
| builder.writeGetterDeclaration(name, |
| isStatic: staticModifier, |
| nameGroupName: 'NAME', |
| returnType: fieldType, |
| returnTypeGroupName: 'TYPE'); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_GETTER, args: [name]); |
| } |
| |
| Future<void> _addFix_createImportUri() async { |
| // TODO(brianwilkerson) Generalize this to allow other valid string literals. |
| // TODO(brianwilkerson) Support the case where the node's parent is a Configuration. |
| if (node is SimpleStringLiteral && node.parent is ImportDirective) { |
| ImportDirective importDirective = node.parent; |
| var source = importDirective.uriSource; |
| if (source != null) { |
| var file = source.fullName; |
| if (isAbsolute(file) && AnalysisEngine.isDartFileName(file)) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(source.fullName, (builder) { |
| builder.addSimpleInsertion(0, '// TODO Implement this library.'); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, |
| DartFixKind.CREATE_FILE, |
| args: [source.shortName], |
| ); |
| } |
| } |
| } |
| } |
| |
| Future<void> _addFix_createLocalVariable() async { |
| if (node is! SimpleIdentifier) { |
| return; |
| } |
| SimpleIdentifier nameNode = node; |
| var name = nameNode.name; |
| // if variable is assigned, convert assignment into declaration |
| if (node.parent is AssignmentExpression) { |
| AssignmentExpression assignment = node.parent; |
| if (assignment.leftHandSide == node && |
| assignment.operator.type == TokenType.EQ && |
| assignment.parent is ExpressionStatement) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleInsertion(node.offset, 'var '); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_LOCAL_VARIABLE, |
| args: [name]); |
| return; |
| } |
| } |
| // prepare target Statement |
| var target = node.thisOrAncestorOfType<Statement>(); |
| if (target == null) { |
| return; |
| } |
| var prefix = utils.getNodePrefix(target); |
| // compute type |
| var type = _inferUndefinedExpressionType(node); |
| if (!(type == null || type is InterfaceType || type is FunctionType)) { |
| return; |
| } |
| // build variable declaration source |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(target.offset, (DartEditBuilder builder) { |
| builder.writeLocalVariableDeclaration(name, |
| nameGroupName: 'NAME', type: type, typeGroupName: 'TYPE'); |
| builder.write(eol); |
| builder.write(prefix); |
| }); |
| builder.addLinkedPosition(range.node(node), 'NAME'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_LOCAL_VARIABLE, |
| args: [name]); |
| } |
| |
| Future<void> _addFix_createMethod() async { |
| if (node is! SimpleIdentifier || node.parent is! MethodInvocation) { |
| return; |
| } |
| var name = (node as SimpleIdentifier).name; |
| var invocation = node.parent as MethodInvocation; |
| // prepare environment |
| Element targetElement; |
| var staticModifier = false; |
| |
| CompilationUnitMember targetNode; |
| var target = invocation.realTarget; |
| var utils = this.utils; |
| if (target is ExtensionOverride) { |
| targetElement = target.staticElement; |
| targetNode = await _getExtensionDeclaration(targetElement); |
| if (targetNode == null) { |
| return; |
| } |
| } else if (target is Identifier && |
| target.staticElement is ExtensionElement) { |
| targetElement = target.staticElement; |
| targetNode = await _getExtensionDeclaration(targetElement); |
| if (targetNode == null) { |
| return; |
| } |
| staticModifier = true; |
| } else if (target == null) { |
| targetElement = unit.declaredElement; |
| var enclosingMember = node.thisOrAncestorOfType<ClassMember>(); |
| if (enclosingMember == null) { |
| // If the undefined identifier isn't inside a class member, then it |
| // doesn't make sense to create a method. |
| return; |
| } |
| targetNode = enclosingMember.parent; |
| staticModifier = _inStaticContext(); |
| } else { |
| var targetClassElement = _getTargetClassElement(target); |
| if (targetClassElement == null) { |
| return; |
| } |
| targetElement = targetClassElement; |
| if (targetClassElement.librarySource.isInSystemLibrary) { |
| return; |
| } |
| // prepare target ClassDeclaration |
| targetNode = await _getClassDeclaration(targetClassElement); |
| if (targetNode == null) { |
| return; |
| } |
| // maybe static |
| if (target is Identifier) { |
| staticModifier = target.staticElement.kind == ElementKind.CLASS; |
| } |
| // use different utils |
| var targetPath = targetClassElement.source.fullName; |
| var targetResolveResult = await session.getResolvedUnit(targetPath); |
| utils = CorrectionUtils(targetResolveResult); |
| } |
| var targetLocation = utils.prepareNewMethodLocation(targetNode); |
| var targetFile = targetElement.source.fullName; |
| // build method source |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(targetFile, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| builder.write(targetLocation.prefix); |
| // maybe "static" |
| if (staticModifier) { |
| builder.write('static '); |
| } |
| // append return type |
| { |
| var type = _inferUndefinedExpressionType(invocation); |
| if (builder.writeType(type, groupName: 'RETURN_TYPE')) { |
| builder.write(' '); |
| } |
| } |
| // append name |
| builder.addLinkedEdit('NAME', (DartLinkedEditBuilder builder) { |
| builder.write(name); |
| }); |
| builder.write('('); |
| builder.writeParametersMatchingArguments(invocation.argumentList); |
| builder.write(') {}'); |
| builder.write(targetLocation.suffix); |
| }); |
| if (targetFile == file) { |
| builder.addLinkedPosition(range.node(node), 'NAME'); |
| } |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_METHOD, args: [name]); |
| } |
| |
| Future<void> _addFix_createMissingOverrides() async { |
| if (node.parent is! ClassDeclaration) { |
| return; |
| } |
| var targetClass = node.parent as ClassDeclaration; |
| var targetClassElement = targetClass.declaredElement; |
| utils.targetClassElement = targetClassElement; |
| var signatures = |
| InheritanceOverrideVerifier.missingOverrides(targetClass).toList(); |
| // sort by name, getters before setters |
| signatures.sort((ExecutableElement a, ExecutableElement b) { |
| var names = compareStrings(a.displayName, b.displayName); |
| if (names != 0) { |
| return names; |
| } |
| if (a.kind == ElementKind.GETTER) { |
| return -1; |
| } |
| return 1; |
| }); |
| var numElements = signatures.length; |
| |
| var location = |
| utils.prepareNewClassMemberLocation(targetClass, (_) => true); |
| |
| var prefix = utils.getIndent(1); |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(location.offset, (DartEditBuilder builder) { |
| // Separator management. |
| var numOfMembersWritten = 0; |
| void addSeparatorBetweenDeclarations() { |
| if (numOfMembersWritten == 0) { |
| builder.write(location.prefix); |
| } else { |
| builder.write(eol); // after the previous member |
| builder.write(eol); // empty line separator |
| builder.write(prefix); |
| } |
| numOfMembersWritten++; |
| } |
| |
| // merge getter/setter pairs into fields |
| for (var i = 0; i < signatures.length; i++) { |
| var element = signatures[i]; |
| if (element.kind == ElementKind.GETTER && i + 1 < signatures.length) { |
| var nextElement = signatures[i + 1]; |
| if (nextElement.kind == ElementKind.SETTER) { |
| // remove this and the next elements, adjust iterator |
| signatures.removeAt(i + 1); |
| signatures.removeAt(i); |
| i--; |
| numElements--; |
| // separator |
| addSeparatorBetweenDeclarations(); |
| // @override |
| builder.write('@override'); |
| builder.write(eol); |
| // add field |
| builder.write(prefix); |
| builder.writeType(element.returnType, required: true); |
| builder.write(' '); |
| builder.write(element.name); |
| builder.write(';'); |
| } |
| } |
| } |
| // add elements |
| for (var element in signatures) { |
| addSeparatorBetweenDeclarations(); |
| builder.writeOverride(element); |
| } |
| builder.write(location.suffix); |
| }); |
| }); |
| changeBuilder.setSelection(Position(file, location.offset)); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_MISSING_OVERRIDES, |
| args: [numElements]); |
| } |
| |
| Future<void> _addFix_createMixin() async { |
| Element prefixElement; |
| String name; |
| SimpleIdentifier nameNode; |
| if (node is SimpleIdentifier) { |
| var parent = node.parent; |
| if (parent is PrefixedIdentifier) { |
| if (parent.parent is InstanceCreationExpression) { |
| return; |
| } |
| PrefixedIdentifier prefixedIdentifier = parent; |
| prefixElement = prefixedIdentifier.prefix.staticElement; |
| if (prefixElement == null) { |
| return; |
| } |
| parent = prefixedIdentifier.parent; |
| nameNode = prefixedIdentifier.identifier; |
| name = prefixedIdentifier.identifier.name; |
| } else if (parent is TypeName && |
| parent.parent is ConstructorName && |
| parent.parent.parent is InstanceCreationExpression) { |
| return; |
| } else { |
| nameNode = node; |
| name = nameNode.name; |
| } |
| if (!_mayBeTypeIdentifier(nameNode)) { |
| return; |
| } |
| } else { |
| return; |
| } |
| // prepare environment |
| Element targetUnit; |
| var prefix = ''; |
| var suffix = ''; |
| var offset = -1; |
| String filePath; |
| if (prefixElement == null) { |
| targetUnit = unit.declaredElement; |
| var enclosingMember = node.thisOrAncestorMatching((node) => |
| node is CompilationUnitMember && node.parent is CompilationUnit); |
| if (enclosingMember == null) { |
| return; |
| } |
| offset = enclosingMember.end; |
| filePath = file; |
| prefix = '$eol$eol'; |
| } else { |
| for (var import in unitLibraryElement.imports) { |
| if (prefixElement is PrefixElement && import.prefix == prefixElement) { |
| var library = import.importedLibrary; |
| if (library != null) { |
| targetUnit = library.definingCompilationUnit; |
| var targetSource = targetUnit.source; |
| try { |
| offset = targetSource.contents.data.length; |
| filePath = targetSource.fullName; |
| prefix = '$eol'; |
| suffix = '$eol'; |
| } on FileSystemException { |
| // If we can't read the file to get the offset, then we can't |
| // create a fix. |
| } |
| break; |
| } |
| } |
| } |
| } |
| if (offset < 0) { |
| return; |
| } |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(filePath, (DartFileEditBuilder builder) { |
| builder.addInsertion(offset, (DartEditBuilder builder) { |
| builder.write(prefix); |
| builder.writeMixinDeclaration(name, nameGroupName: 'NAME'); |
| builder.write(suffix); |
| }); |
| if (prefixElement == null) { |
| builder.addLinkedPosition(range.node(node), 'NAME'); |
| } |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_MIXIN, args: [name]); |
| } |
| |
| Future<void> _addFix_createNoSuchMethod() async { |
| if (node.parent is! ClassDeclaration) { |
| return; |
| } |
| var targetClass = node.parent as ClassDeclaration; |
| // prepare environment |
| var prefix = utils.getIndent(1); |
| var insertOffset = targetClass.end - 1; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(insertOffset, (DartEditBuilder builder) { |
| builder.selectHere(); |
| // insert empty line before existing member |
| if (targetClass.members.isNotEmpty) { |
| builder.write(eol); |
| } |
| // append method |
| builder.write(prefix); |
| builder.write( |
| 'noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);'); |
| builder.write(eol); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_NO_SUCH_METHOD); |
| } |
| |
| Future<void> _addFix_createPartUri() async { |
| // TODO(brianwilkerson) Generalize this to allow other valid string literals. |
| if (node is SimpleStringLiteral && node.parent is PartDirective) { |
| PartDirective partDirective = node.parent; |
| var source = partDirective.uriSource; |
| if (source != null) { |
| var libName = unitLibraryElement.name; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(source.fullName, |
| (DartFileEditBuilder builder) { |
| // TODO(brianwilkerson) Consider using the URI rather than name |
| builder.addSimpleInsertion(0, 'part of $libName;$eol$eol'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_FILE, |
| args: [source.shortName]); |
| } |
| } |
| } |
| |
| Future<void> _addFix_createSetter() async { |
| if (node is! SimpleIdentifier) { |
| return; |
| } |
| SimpleIdentifier nameNode = node; |
| if (!nameNode.inSetterContext()) { |
| return; |
| } |
| // prepare target |
| Expression target; |
| { |
| var nameParent = nameNode.parent; |
| if (nameParent is PrefixedIdentifier) { |
| target = nameParent.prefix; |
| } else if (nameParent is PropertyAccess) { |
| target = nameParent.realTarget; |
| } |
| } |
| // prepare target element |
| var staticModifier = false; |
| Element targetElement; |
| if (target is ExtensionOverride) { |
| targetElement = target.staticElement; |
| } else if (target is Identifier && |
| target.staticElement is ExtensionElement) { |
| targetElement = target.staticElement; |
| staticModifier = true; |
| } else if (target != null) { |
| // prepare target interface type |
| var targetType = target.staticType; |
| if (targetType is! InterfaceType) { |
| return; |
| } |
| targetElement = targetType.element; |
| // maybe static |
| if (target is Identifier) { |
| var targetIdentifier = target; |
| var targetElement = targetIdentifier.staticElement; |
| staticModifier = targetElement?.kind == ElementKind.CLASS; |
| } |
| } else { |
| targetElement = |
| getEnclosingClassElement(node) ?? getEnclosingExtensionElement(node); |
| if (targetElement == null) { |
| return; |
| } |
| staticModifier = _inStaticContext(); |
| } |
| if (targetElement.librarySource.isInSystemLibrary) { |
| return; |
| } |
| // prepare target declaration |
| var targetDeclarationResult = |
| await sessionHelper.getElementDeclaration(targetElement); |
| if (targetDeclarationResult == null) { |
| return; |
| } |
| if (targetDeclarationResult.node is! ClassOrMixinDeclaration && |
| targetDeclarationResult.node is! ExtensionDeclaration) { |
| return; |
| } |
| CompilationUnitMember targetNode = targetDeclarationResult.node; |
| // prepare location |
| var targetLocation = CorrectionUtils(targetDeclarationResult.resolvedUnit) |
| .prepareNewGetterLocation(targetNode); // Rename to "AccessorLocation" |
| // build method source |
| var targetSource = targetElement.source; |
| var targetFile = targetSource.fullName; |
| var name = nameNode.name; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(targetFile, (DartFileEditBuilder builder) { |
| builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { |
| var parameterTypeNode = climbPropertyAccess(nameNode); |
| var parameterType = _inferUndefinedExpressionType(parameterTypeNode); |
| builder.write(targetLocation.prefix); |
| builder.writeSetterDeclaration(name, |
| isStatic: staticModifier, |
| nameGroupName: 'NAME', |
| parameterType: parameterType, |
| parameterTypeGroupName: 'TYPE'); |
| builder.write(targetLocation.suffix); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_SETTER, args: [name]); |
| } |
| |
| Future<void> _addFix_extendClassForMixin() async { |
| var declaration = node.thisOrAncestorOfType<ClassDeclaration>(); |
| if (declaration != null && declaration.extendsClause == null) { |
| // TODO(brianwilkerson) Find a way to pass in the name of the class |
| // without needing to parse the message. |
| var message = error.message; |
| var endIndex = message.lastIndexOf("'"); |
| var startIndex = message.lastIndexOf("'", endIndex - 1) + 1; |
| var typeName = message.substring(startIndex, endIndex); |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleInsertion( |
| declaration.typeParameters?.end ?? declaration.name.end, |
| ' extends $typeName'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.EXTEND_CLASS_FOR_MIXIN, |
| args: [typeName]); |
| } |
| } |
| |
| Future<void> _addFix_illegalAsyncReturnType() async { |
| // prepare the existing type |
| var typeName = node.thisOrAncestorOfType<TypeAnnotation>(); |
| var typeProvider = this.typeProvider; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.replaceTypeWithFuture(typeName, typeProvider); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REPLACE_RETURN_TYPE_FUTURE); |
| } |
| |
| Future<void> _addFix_importAsync() async { |
| await _addFix_importLibrary( |
| DartFixKind.IMPORT_ASYNC, Uri.parse('dart:async')); |
| } |
| |
| Future<void> _addFix_importLibrary(FixKind kind, Uri library, |
| [String relativeURI]) async { |
| String uriText; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| uriText = builder.importLibrary(library); |
| }); |
| _addFixFromBuilder(changeBuilder, kind, args: [uriText]); |
| |
| if (relativeURI != null && relativeURI.isNotEmpty) { |
| var changeBuilder2 = _newDartChangeBuilder(); |
| await changeBuilder2.addFileEdit(file, (DartFileEditBuilder builder) { |
| if (builder is DartFileEditBuilderImpl) { |
| builder.importLibraryWithRelativeUri(relativeURI); |
| } |
| }); |
| _addFixFromBuilder(changeBuilder2, kind, args: [relativeURI]); |
| } |
| } |
| |
| Future<void> _addFix_importLibrary_withElement( |
| String name, |
| List<ElementKind> elementKinds, |
| List<TopLevelDeclarationKind> kinds2) async { |
| // ignore if private |
| if (name.startsWith('_')) { |
| return; |
| } |
| // may be there is an existing import, |
| // but it is with prefix and we don't use this prefix |
| var alreadyImportedWithPrefix = <String>{}; |
| for (var imp in unitLibraryElement.imports) { |
| // prepare element |
| var libraryElement = imp.importedLibrary; |
| var element = getExportedElement(libraryElement, name); |
| if (element == null) { |
| continue; |
| } |
| if (element is PropertyAccessorElement) { |
| element = (element as PropertyAccessorElement).variable; |
| } |
| if (!elementKinds.contains(element.kind)) { |
| continue; |
| } |
| // may be apply prefix |
| var prefix = imp.prefix; |
| if (prefix != null) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement( |
| range.startLength(node, 0), '${prefix.displayName}.'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.IMPORT_LIBRARY_PREFIX, |
| args: [libraryElement.displayName, prefix.displayName]); |
| continue; |
| } |
| // may be update "show" directive |
| var combinators = imp.combinators; |
| if (combinators.length == 1 && combinators[0] is ShowElementCombinator) { |
| var showCombinator = combinators[0] as ShowElementCombinator; |
| // prepare new set of names to show |
| Set<String> showNames = SplayTreeSet<String>(); |
| showNames.addAll(showCombinator.shownNames); |
| showNames.add(name); |
| // prepare library name - unit name or 'dart:name' for SDK library |
| var libraryName = |
| libraryElement.definingCompilationUnit.source.uri.toString(); |
| if (libraryElement.isInSdk) { |
| libraryName = libraryElement.source.shortName; |
| } |
| // don't add this library again |
| alreadyImportedWithPrefix.add(libraryElement.source.fullName); |
| // update library |
| var newShowCode = 'show ${showNames.join(', ')}'; |
| var offset = showCombinator.offset; |
| var length = showCombinator.end - offset; |
| var libraryFile = context.resolveResult.libraryElement.source.fullName; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(libraryFile, |
| (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement( |
| SourceRange(offset, length), newShowCode); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.IMPORT_LIBRARY_SHOW, |
| args: [libraryName]); |
| } |
| } |
| // Find new top-level declarations. |
| { |
| var declarations = context.getTopLevelDeclarations(name); |
| for (var declaration in declarations) { |
| // Check the kind. |
| if (!kinds2.contains(declaration.kind)) { |
| continue; |
| } |
| // Check the source. |
| if (alreadyImportedWithPrefix.contains(declaration.path)) { |
| continue; |
| } |
| // Check that the import doesn't end with '.template.dart' |
| if (declaration.uri.path.endsWith('.template.dart')) { |
| continue; |
| } |
| // Compute the fix kind. |
| FixKind fixKind; |
| if (declaration.uri.isScheme('dart')) { |
| fixKind = DartFixKind.IMPORT_LIBRARY_SDK; |
| } else if (_isLibSrcPath(declaration.path)) { |
| // Bad: non-API. |
| fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT3; |
| } else if (declaration.isExported) { |
| // Ugly: exports. |
| fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT2; |
| } else { |
| // Good: direct declaration. |
| fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT1; |
| } |
| // Add the fix. |
| var relativeURI = |
| _getRelativeURIFromLibrary(unitLibraryElement, declaration.path); |
| await _addFix_importLibrary(fixKind, declaration.uri, relativeURI); |
| } |
| } |
| } |
| |
| Future<void> _addFix_importLibrary_withExtension() async { |
| if (node is SimpleIdentifier) { |
| var extensionName = (node as SimpleIdentifier).name; |
| await _addFix_importLibrary_withElement( |
| extensionName, |
| const [ElementKind.EXTENSION], |
| const [TopLevelDeclarationKind.extension]); |
| } |
| } |
| |
| Future<void> _addFix_importLibrary_withFunction() async { |
| if (node is SimpleIdentifier) { |
| if (node.parent is MethodInvocation) { |
| var invocation = node.parent as MethodInvocation; |
| if (invocation.realTarget != null || invocation.methodName != node) { |
| return; |
| } |
| } |
| |
| var name = (node as SimpleIdentifier).name; |
| await _addFix_importLibrary_withElement(name, const [ |
| ElementKind.FUNCTION, |
| ElementKind.TOP_LEVEL_VARIABLE |
| ], const [ |
| TopLevelDeclarationKind.function, |
| TopLevelDeclarationKind.variable |
| ]); |
| } |
| } |
| |
| Future<void> _addFix_importLibrary_withTopLevelVariable() async { |
| if (node is SimpleIdentifier) { |
| var name = (node as SimpleIdentifier).name; |
| await _addFix_importLibrary_withElement( |
| name, |
| const [ElementKind.TOP_LEVEL_VARIABLE], |
| const [TopLevelDeclarationKind.variable]); |
| } |
| } |
| |
| Future<void> _addFix_importLibrary_withType() async { |
| if (_mayBeTypeIdentifier(node)) { |
| var typeName = (node as SimpleIdentifier).name; |
| await _addFix_importLibrary_withElement( |
| typeName, |
| const [ElementKind.CLASS, ElementKind.FUNCTION_TYPE_ALIAS], |
| const [TopLevelDeclarationKind.type]); |
| } else if (_mayBeImplicitConstructor(node)) { |
| var typeName = (node as SimpleIdentifier).name; |
| await _addFix_importLibrary_withElement(typeName, |
| const [ElementKind.CLASS], const [TopLevelDeclarationKind.type]); |
| } |
| } |
| |
| Future<void> _addFix_insertSemicolon() async { |
| if (error.message.contains("';'")) { |
| if (_isAwaitNode()) { |
| return; |
| } |
| var insertOffset = error.offset + error.length; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleInsertion(insertOffset, ';'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.INSERT_SEMICOLON); |
| } |
| } |
| |
| Future<void> _addFix_isNotNull() async { |
| if (coveredNode is IsExpression) { |
| var isExpression = coveredNode as IsExpression; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder |
| .addReplacement(range.endEnd(isExpression.expression, isExpression), |
| (DartEditBuilder builder) { |
| builder.write(' != null'); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.USE_NOT_EQ_NULL); |
| } |
| } |
| |
| Future<void> _addFix_isNull() async { |
| if (coveredNode is IsExpression) { |
| var isExpression = coveredNode as IsExpression; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder |
| .addReplacement(range.endEnd(isExpression.expression, isExpression), |
| (DartEditBuilder builder) { |
| builder.write(' == null'); |
| }); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.USE_EQ_EQ_NULL); |
| } |
| } |
| |
| Future<void> _addFix_makeEnclosingClassAbstract() async { |
| var enclosingClass = node.thisOrAncestorOfType<ClassDeclaration>(); |
| if (enclosingClass == null) { |
| return; |
| } |
| var className = enclosingClass.name.name; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleInsertion( |
| enclosingClass.classKeyword.offset, 'abstract '); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.MAKE_CLASS_ABSTRACT, |
| args: [className]); |
| } |
| |
| Future<void> _addFix_makeFieldNotFinal() async { |
| var node = this.node; |
| if (node is SimpleIdentifier && |
| node.staticElement is PropertyAccessorElement) { |
| PropertyAccessorElement getter = node.staticElement; |
| if (getter.isGetter && |
| getter.isSynthetic && |
| !getter.variable.isSynthetic && |
| getter.variable.setter == null && |
| getter.enclosingElement is ClassElement) { |
| var declarationResult = |
| await sessionHelper.getElementDeclaration(getter.variable); |
| var variable = declarationResult.node; |
| if (variable is VariableDeclaration && |
| variable.parent is VariableDeclarationList && |
| variable.parent.parent is FieldDeclaration) { |
| VariableDeclarationList declarationList = variable.parent; |
| var keywordToken = declarationList.keyword; |
| if (declarationList.variables.length == 1 && |
| keywordToken.keyword == Keyword.FINAL) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, |
| (DartFileEditBuilder builder) { |
| if (declarationList.type != null) { |
| builder.addReplacement( |
| range.startStart(keywordToken, declarationList.type), |
| (DartEditBuilder builder) {}); |
| } else { |
| builder.addReplacement(range.startStart(keywordToken, variable), |
| (DartEditBuilder builder) { |
| builder.write('var '); |
| }); |
| } |
| }); |
| var fieldName = getter.variable.displayName; |
| _addFixFromBuilder(changeBuilder, DartFixKind.MAKE_FIELD_NOT_FINAL, |
| args: [fieldName]); |
| } |
| } |
| } |
| } |
| } |
| |
| Future<void> _addFix_makeVariableNotFinal() async { |
| var node = this.node; |
| if (node is SimpleIdentifier && |
| node.staticElement is LocalVariableElement) { |
| LocalVariableElement variable = node.staticElement; |
| var id = NodeLocator(variable.nameOffset).searchWithin(unit); |
| var decl = id?.parent; |
| if (decl is VariableDeclaration && |
| decl.parent is VariableDeclarationList) { |
| VariableDeclarationList declarationList = decl.parent; |
| var keywordToken = declarationList.keyword; |
| if (declarationList.variables.length == 1 && |
| keywordToken.keyword == Keyword.FINAL) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| if (declarationList.type != null) { |
| builder.addDeletion( |
| range.startStart(keywordToken, declarationList.type)); |
| } else { |
| builder.addSimpleReplacement(range.token(keywordToken), 'var'); |
| } |
| }); |
| declarationList.variables[0].name.name; |
| var varName = declarationList.variables[0].name.name; |
| _addFixFromBuilder(changeBuilder, DartFixKind.MAKE_VARIABLE_NOT_FINAL, |
| args: [varName]); |
| } |
| } |
| } |
| } |
| |
| Future<void> _addFix_moveTypeArgumentsToClass() async { |
| if (coveredNode is TypeArgumentList) { |
| TypeArgumentList typeArguments = coveredNode; |
| if (typeArguments.parent is! InstanceCreationExpression) { |
| return; |
| } |
| InstanceCreationExpression creation = typeArguments.parent; |
| var typeName = creation.constructorName.type; |
| if (typeName.typeArguments != null) { |
| return; |
| } |
| var element = typeName.type.element; |
| if (element is ClassElement && |
| element.typeParameters != null && |
| element.typeParameters.length == typeArguments.arguments.length) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| var argumentText = utils.getNodeText(typeArguments); |
| builder.addSimpleInsertion(typeName.end, argumentText); |
| builder.addDeletion(range.node(typeArguments)); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.MOVE_TYPE_ARGUMENTS_TO_CLASS); |
| } |
| } |
| } |
| |
| Future<void> _addFix_nonBoolCondition_addNotNull() async { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleInsertion(error.offset + error.length, ' != null'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.ADD_NE_NULL); |
| } |
| |
| Future<void> _addFix_qualifyReference() async { |
| if (node is! SimpleIdentifier) { |
| return; |
| } |
| SimpleIdentifier memberName = node; |
| var parent = node.parent; |
| AstNode target; |
| if (parent is MethodInvocation && node == parent.methodName) { |
| target = parent.target; |
| } else if (parent is PropertyAccess && node == parent.propertyName) { |
| target = parent.target; |
| } |
| if (target != null) { |
| return; |
| } |
| var enclosingElement = memberName.staticElement.enclosingElement; |
| if (enclosingElement.library != unitLibraryElement) { |
| // TODO(brianwilkerson) Support qualifying references to members defined |
| // in other libraries. `DartEditBuilder` currently defines the method |
| // `writeType`, which is close, but we also need to handle extensions, |
| // which don't have a type. |
| return; |
| } |
| var containerName = enclosingElement.name; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleInsertion(node.offset, '$containerName.'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.QUALIFY_REFERENCE, |
| args: ['$containerName.${memberName.name}']); |
| } |
| |
| Future<void> _addFix_removeAnnotation() async { |
| void addFix(Annotation node) async { |
| if (node == null) { |
| return; |
| } |
| var followingToken = node.endToken.next; |
| followingToken = followingToken.precedingComments ?? followingToken; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.startStart(node, followingToken)); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_ANNOTATION, |
| args: [node.name.name]); |
| } |
| |
| Annotation findAnnotation( |
| NodeList<Annotation> metadata, String targetName) { |
| return metadata.firstWhere( |
| (annotation) => annotation.name.name == targetName, |
| orElse: () => null); |
| } |
| |
| var node = coveredNode; |
| if (node is Annotation) { |
| await addFix(node); |
| } else if (node is DefaultFormalParameter) { |
| await addFix(findAnnotation(node.parameter.metadata, 'required')); |
| } else if (node is NormalFormalParameter) { |
| await addFix(findAnnotation(node.metadata, 'required')); |
| } else if (node is DeclaredSimpleIdentifier) { |
| var parent = node.parent; |
| if (parent is MethodDeclaration) { |
| await addFix(findAnnotation(parent.metadata, 'override')); |
| } else if (parent is VariableDeclaration) { |
| var fieldDeclaration = parent.thisOrAncestorOfType<FieldDeclaration>(); |
| if (fieldDeclaration != null) { |
| await addFix(findAnnotation(fieldDeclaration.metadata, 'override')); |
| } |
| } |
| } |
| } |
| |
| Future<void> _addFix_removeDeadCode() async { |
| var coveringNode = coveredNode; |
| if (coveringNode is Expression) { |
| var parent = coveredNode.parent; |
| if (parent is BinaryExpression) { |
| if (parent.rightOperand == coveredNode) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.endEnd(parent.leftOperand, coveredNode)); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_DEAD_CODE); |
| } |
| } |
| } else if (coveringNode is Block) { |
| var block = coveringNode; |
| var statementsToRemove = <Statement>[]; |
| var errorRange = SourceRange(errorOffset, errorLength); |
| for (var statement in block.statements) { |
| if (range.node(statement).intersects(errorRange)) { |
| statementsToRemove.add(statement); |
| } |
| } |
| if (statementsToRemove.isNotEmpty) { |
| var rangeToRemove = utils.getLinesRangeStatements(statementsToRemove); |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(rangeToRemove); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_DEAD_CODE); |
| } |
| } else if (coveringNode is Statement) { |
| var rangeToRemove = |
| utils.getLinesRangeStatements(<Statement>[coveringNode]); |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(rangeToRemove); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_DEAD_CODE); |
| } else if (coveringNode is CatchClause) { |
| TryStatement tryStatement = coveringNode.parent; |
| var catchClauses = tryStatement.catchClauses; |
| var index = catchClauses.indexOf(coveringNode); |
| var previous = index == 0 ? tryStatement.body : catchClauses[index - 1]; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.endEnd(previous, coveringNode)); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_DEAD_CODE); |
| } |
| } |
| |
| Future<void> _addFix_removeNameFromCombinator() async { |
| SourceRange rangeForCombinator(Combinator combinator) { |
| var parent = combinator.parent; |
| if (parent is NamespaceDirective) { |
| var combinators = parent.combinators; |
| if (combinators.length == 1) { |
| var previousToken = |
| combinator.parent.findPrevious(combinator.beginToken); |
| if (previousToken != null) { |
| return range.endEnd(previousToken, combinator); |
| } |
| return null; |
| } |
| var index = combinators.indexOf(combinator); |
| if (index < 0) { |
| return null; |
| } else if (index == combinators.length - 1) { |
| return range.endEnd(combinators[index - 1], combinator); |
| } |
| return range.startStart(combinator, combinators[index + 1]); |
| } |
| return null; |
| } |
| |
| SourceRange rangeForNameInCombinator( |
| Combinator combinator, SimpleIdentifier name) { |
| NodeList<SimpleIdentifier> names; |
| if (combinator is HideCombinator) { |
| names = combinator.hiddenNames; |
| } else if (combinator is ShowCombinator) { |
| names = combinator.shownNames; |
| } else { |
| return null; |
| } |
| if (names.length == 1) { |
| return rangeForCombinator(combinator); |
| } |
| var index = names.indexOf(name); |
| if (index < 0) { |
| return null; |
| } else if (index == names.length - 1) { |
| return range.endEnd(names[index - 1], name); |
| } |
| return range.startStart(name, names[index + 1]); |
| } |
| |
| var node = coveredNode; |
| if (node is SimpleIdentifier) { |
| var parent = coveredNode.parent; |
| if (parent is Combinator) { |
| var rangeToRemove = rangeForNameInCombinator(parent, node); |
| if (rangeToRemove == null) { |
| return; |
| } |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(rangeToRemove); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.REMOVE_NAME_FROM_COMBINATOR, |
| args: [parent is HideCombinator ? 'hide' : 'show']); |
| } |
| } |
| } |
| |
| Future<void> _addFix_removeParameters_inGetterDeclaration() async { |
| if (node is MethodDeclaration) { |
| // Support for the analyzer error. |
| var method = node as MethodDeclaration; |
| var name = method.name; |
| var body = method.body; |
| if (name != null && body != null) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement(range.endStart(name, body), ' '); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.REMOVE_PARAMETERS_IN_GETTER_DECLARATION); |
| } |
| } else if (node is FormalParameterList) { |
| // Support for the fasta error. |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.node(node)); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.REMOVE_PARAMETERS_IN_GETTER_DECLARATION); |
| } |
| } |
| |
| Future<void> _addFix_removeParentheses_inGetterInvocation() async { |
| var invocation = coveredNode?.parent; |
| if (invocation is FunctionExpressionInvocation) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.node(invocation.argumentList)); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.REMOVE_PARENTHESIS_IN_GETTER_INVOCATION); |
| } |
| } |
| |
| Future<void> _addFix_removeTypeArguments() async { |
| if (coveredNode is TypeArgumentList) { |
| TypeArgumentList typeArguments = coveredNode; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.node(typeArguments)); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_TYPE_ARGUMENTS); |
| } |
| } |
| |
| Future<void> _addFix_removeUnnecessaryCast() async { |
| if (coveredNode is! AsExpression) { |
| return; |
| } |
| var asExpression = coveredNode as AsExpression; |
| var expression = asExpression.expression; |
| var expressionPrecedence = getExpressionPrecedence(expression); |
| // remove 'as T' from 'e as T' |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.endEnd(expression, asExpression)); |
| _removeEnclosingParentheses(builder, asExpression, expressionPrecedence); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_UNNECESSARY_CAST); |
| } |
| |
| Future<void> _addFix_removeUnusedCatchClause() async { |
| if (node is SimpleIdentifier) { |
| var catchClause = node.parent; |
| if (catchClause is CatchClause && |
| catchClause.exceptionParameter == node) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion( |
| range.startStart(catchClause.catchKeyword, catchClause.body)); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.REMOVE_UNUSED_CATCH_CLAUSE); |
| } |
| } |
| } |
| |
| Future<void> _addFix_removeUnusedCatchStack() async { |
| if (node is SimpleIdentifier) { |
| var catchClause = node.parent; |
| if (catchClause is CatchClause && |
| catchClause.stackTraceParameter == node && |
| catchClause.exceptionParameter != null) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder |
| .addDeletion(range.endEnd(catchClause.exceptionParameter, node)); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.REMOVE_UNUSED_CATCH_STACK); |
| } |
| } |
| } |
| |
| Future<void> _addFix_removeUnusedImport() async { |
| // prepare ImportDirective |
| var importDirective = node.thisOrAncestorOfType<ImportDirective>(); |
| if (importDirective == null) { |
| return; |
| } |
| // remove the whole line with import |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(utils.getLinesRange(range.node(importDirective))); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_UNUSED_IMPORT); |
| } |
| |
| Future<void> _addFix_removeUnusedLabel() async { |
| final parent = node.parent; |
| if (parent is Label) { |
| var nextToken = parent.endToken.next; |
| final changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addDeletion(range.startStart(parent, nextToken)); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REMOVE_UNUSED_LABEL); |
| } |
| } |
| |
| Future<void> _addFix_replaceVarWithDynamic() async { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement(range.error(error), 'dynamic'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REPLACE_VAR_WITH_DYNAMIC); |
| } |
| |
| Future<void> _addFix_replaceWithConstInstanceCreation() async { |
| if (coveredNode is InstanceCreationExpression) { |
| var instanceCreation = coveredNode as InstanceCreationExpression; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| if (instanceCreation.keyword == null) { |
| builder.addSimpleInsertion( |
| instanceCreation.constructorName.offset, 'const'); |
| } else { |
| builder.addSimpleReplacement( |
| range.token(instanceCreation.keyword), 'const'); |
| } |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.USE_CONST); |
| } |
| } |
| |
| Future<void> _addFix_replaceWithExtensionName() async { |
| if (node is! SimpleIdentifier) { |
| return; |
| } |
| var parent = node.parent; |
| AstNode target; |
| if (parent is MethodInvocation && node == parent.methodName) { |
| target = parent.target; |
| } else if (parent is PropertyAccess && node == parent.propertyName) { |
| target = parent.target; |
| } |
| if (target is! ExtensionOverride) { |
| return; |
| } |
| ExtensionOverride override = target; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement( |
| range.node(override), utils.getNodeText(override.extensionName)); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.REPLACE_WITH_EXTENSION_NAME, |
| args: [override.extensionName.name]); |
| } |
| |
| Future<void> _addFix_undefinedClass_useSimilar() async { |
| var node = this.node; |
| // Prepare the optional import prefix name. |
| String prefixName; |
| if (node is SimpleIdentifier && node.staticElement is PrefixElement) { |
| var parent = node.parent; |
| if (parent is PrefixedIdentifier && |
| parent.prefix == node && |
| parent.parent is TypeName) { |
| prefixName = (node as SimpleIdentifier).name; |
| node = parent.identifier; |
| } |
| } |
| // Process if looks like a type. |
| if (_mayBeTypeIdentifier(node)) { |
| // Prepare for selecting the closest element. |
| var name = (node as SimpleIdentifier).name; |
| var finder = _ClosestElementFinder( |
| name, |
| (Element element) => element is ClassElement, |
| MAX_LEVENSHTEIN_DISTANCE); |
| // Check elements of this library. |
| if (prefixName == null) { |
| for (var unit in unitLibraryElement.units) { |
| finder._updateList(unit.types); |
| } |
| } |
| // Check elements from imports. |
| for (var importElement in unitLibraryElement.imports) { |
| if (importElement.prefix?.name == prefixName) { |
| var namespace = getImportNamespace(importElement); |
| finder._updateList(namespace.values); |
| } |
| } |
| // If we have a close enough element, suggest to use it. |
| if (finder._element != null) { |
| var closestName = finder._element.name; |
| if (closestName != null) { |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement(range.node(node), closestName); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CHANGE_TO, |
| args: [closestName]); |
| } |
| } |
| } |
| } |
| |
| Future<void> _addFix_undefinedClassAccessor_useSimilar() async { |
| var node = this.node; |
| if (node is SimpleIdentifier) { |
| // prepare target |
| Expression target; |
| if (node.parent is PrefixedIdentifier) { |
| target = (node.parent as PrefixedIdentifier).prefix; |
| } else if (node.parent is PropertyAccess) { |
| target = (node.parent as PropertyAccess).target; |
| } |
| // find getter |
| if (node.inGetterContext()) { |
| await _addFix_undefinedClassMember_useSimilar(target, |
| (Element element) { |
| return element is PropertyAccessorElement && element.isGetter || |
| element is FieldElement && element.getter != null; |
| }); |
| } |
| // find setter |
| if (node.inSetterContext()) { |
| await _addFix_undefinedClassMember_useSimilar(target, |
| (Element element) { |
| return element is PropertyAccessorElement && element.isSetter || |
| element is FieldElement && element.setter != null; |
| }); |
| } |
| } |
| } |
| |
| Future<void> _addFix_undefinedClassMember_useSimilar( |
| Expression target, ElementPredicate predicate) async { |
| if (node is SimpleIdentifier) { |
| var name = (node as SimpleIdentifier).name; |
| var finder = |
| _ClosestElementFinder(name, predicate, MAX_LEVENSHTEIN_DISTANCE); |
| // unqualified invocation |
| if (target == null) { |
| var clazz = node.thisOrAncestorOfType<ClassDeclaration>(); |
| if (clazz != null) { |
| var classElement = clazz.declaredElement; |
| _updateFinderWithClassMembers(finder, classElement); |
| } |
| } else if (target is ExtensionOverride) { |
| _updateFinderWithExtensionMembers(finder, target.staticElement); |
| } else if (target is Identifier && |
| target.staticElement is ExtensionElement) { |
| _updateFinderWithExtensionMembers(finder, target.staticElement); |
| } else { |
| var classElement = _getTargetClassElement(target); |
| if (classElement != null) { |
| _updateFinderWithClassMembers(finder, classElement); |
| } |
| } |
| // if we have close enough element, suggest to use it |
| if (finder._element != null) { |
| var closestName = finder._element.displayName; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement(range.node(node), closestName); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CHANGE_TO, |
| args: [closestName]); |
| } |
| } |
| } |
| |
| Future<void> _addFix_undefinedFunction_create() async { |
| // should be the name of the invocation |
| if (node is SimpleIdentifier && node.parent is MethodInvocation) { |
| } else { |
| return; |
| } |
| var name = (node as SimpleIdentifier).name; |
| var invocation = node.parent as MethodInvocation; |
| // function invocation has no target |
| var target = invocation.realTarget; |
| if (target != null) { |
| return; |
| } |
| // prepare environment |
| int insertOffset; |
| String sourcePrefix; |
| AstNode enclosingMember = |
| node.thisOrAncestorOfType<CompilationUnitMember>(); |
| insertOffset = enclosingMember.end; |
| sourcePrefix = '$eol$eol'; |
| utils.targetClassElement = null; |
| // build method source |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addInsertion(insertOffset, (DartEditBuilder builder) { |
| builder.write(sourcePrefix); |
| // append return type |
| { |
| var type = _inferUndefinedExpressionType(invocation); |
| if (builder.writeType(type, groupName: 'RETURN_TYPE')) { |
| builder.write(' '); |
| } |
| } |
| // append name |
| builder.addLinkedEdit('NAME', (DartLinkedEditBuilder builder) { |
| builder.write(name); |
| }); |
| builder.write('('); |
| builder.writeParametersMatchingArguments(invocation.argumentList); |
| builder.write(') {$eol}'); |
| }); |
| builder.addLinkedPosition(range.node(node), 'NAME'); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_FUNCTION, |
| args: [name]); |
| } |
| |
| Future<void> _addFix_undefinedFunction_useSimilar() async { |
| var node = this.node; |
| if (node is SimpleIdentifier) { |
| // Prepare the optional import prefix name. |
| String prefixName; |
| { |
| var invocation = node.parent; |
| if (invocation is MethodInvocation && invocation.methodName == node) { |
| var target = invocation.target; |
| if (target is SimpleIdentifier && |
| target.staticElement is PrefixElement) { |
| prefixName = target.name; |
| } |
| } |
| } |
| // Prepare for selecting the closest element. |
| var finder = _ClosestElementFinder( |
| node.name, |
| (Element element) => element is FunctionElement, |
| MAX_LEVENSHTEIN_DISTANCE); |
| // Check to this library units. |
| if (prefixName == null) { |
| for (var unit in unitLibraryElement.units) { |
| finder._updateList(unit.functions); |
| } |
| } |
| // Check unprefixed imports. |
| for (var importElement in unitLibraryElement.imports) { |
| if (importElement.prefix?.name == prefixName) { |
| var namespace = getImportNamespace(importElement); |
| finder._updateList(namespace.values); |
| } |
| } |
| // If we have a close enough element, suggest to use it. |
| if (finder._element != null) { |
| var closestName = finder._element.name; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addSimpleReplacement(range.node(node), closestName); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CHANGE_TO, |
| args: [closestName]); |
| } |
| } |
| } |
| |
| Future<void> _addFix_undefinedMethod_useSimilar() async { |
| if (node.parent is MethodInvocation) { |
| var invocation = node.parent as MethodInvocation; |
| await _addFix_undefinedClassMember_useSimilar(invocation.realTarget, |
| (Element element) => element is MethodElement && !element.isOperator); |
| } |
| } |
| |
| Future<void> _addFix_updateSdkConstraints(String minimumVersion) async { |
| var context = resourceProvider.pathContext; |
| File pubspecFile; |
| var folder = resourceProvider.getFolder(context.dirname(file)); |
| while (folder != null) { |
| pubspecFile = folder.getChildAssumingFile('pubspec.yaml'); |
| if (pubspecFile.exists) { |
| break; |
| } |
| pubspecFile = null; |
| folder = folder.parent; |
| } |
| if (pubspecFile == null) { |
| return; |
| } |
| var extractor = SdkConstraintExtractor(pubspecFile); |
| var text = extractor.constraintText(); |
| var offset = extractor.constraintOffset(); |
| if (text == null || offset < 0) { |
| return; |
| } |
| var length = text.length; |
| String newText; |
| var spaceOffset = text.indexOf(' '); |
| if (spaceOffset >= 0) { |
| length = spaceOffset; |
| } |
| if (text == 'any') { |
| newText = '^$minimumVersion'; |
| } else if (text.startsWith('^')) { |
| newText = '^$minimumVersion'; |
| } else if (text.startsWith('>=')) { |
| newText = '>=$minimumVersion'; |
| } else if (text.startsWith('>')) { |
| newText = '>=$minimumVersion'; |
| } |
| if (newText == null) { |
| return; |
| } |
| var changeBuilder = ChangeBuilder(); |
| await changeBuilder.addFileEdit(pubspecFile.path, (builder) { |
| builder.addSimpleReplacement(SourceRange(offset, length), newText); |
| }); |
| _addFixFromBuilder(changeBuilder, DartFixKind.UPDATE_SDK_CONSTRAINTS); |
| } |
| |
| Future<void> _addFix_useEffectiveIntegerDivision() async { |
| for (var n = node; n != null; n = n.parent) { |
| if (n is MethodInvocation && |
| n.offset == errorOffset && |
| n.length == errorLength) { |
| var target = (n as MethodInvocation).target.unParenthesized; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| // replace "/" with "~/" |
| var binary = target as BinaryExpression; |
| builder.addSimpleReplacement(range.token(binary.operator), '~/'); |
| // remove everything before and after |
| builder.addDeletion(range.startStart(n, binary.leftOperand)); |
| builder.addDeletion(range.endEnd(binary.rightOperand, n)); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, DartFixKind.USE_EFFECTIVE_INTEGER_DIVISION); |
| // done |
| break; |
| } |
| } |
| } |
| |
| /// Adds a fix that replaces [target] with a reference to the class declaring |
| /// the given [element]. |
| Future<void> _addFix_useStaticAccess(AstNode target, Element element) async { |
| var declaringElement = element.enclosingElement; |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { |
| builder.addReplacement(range.node(target), (DartEditBuilder builder) { |
| builder.writeReference(declaringElement); |
| }); |
| }); |
| _addFixFromBuilder( |
| changeBuilder, |
| DartFixKind.CHANGE_TO_STATIC_ACCESS, |
| args: [declaringElement.name], |
| ); |
| } |
| |
| Future<void> _addFix_useStaticAccess_method() async { |
| if (node is SimpleIdentifier && node.parent is MethodInvocation) { |
| var invocation = node.parent as MethodInvocation; |
| if (invocation.methodName == node) { |
| var target = invocation.target; |
| var invokedElement = invocation.methodName.staticElement; |
| await _addFix_useStaticAccess(target, invokedElement); |
| } |
| } |
| } |
| |
| Future<void> _addFix_useStaticAccess_property() async { |
| if (node is SimpleIdentifier && node.parent is PrefixedIdentifier) { |
| var prefixed = node.parent as PrefixedIdentifier; |
| if (prefixed.identifier == node) { |
| Expression target = prefixed.prefix; |
| var invokedElement = prefixed.identifier.staticElement; |
| await _addFix_useStaticAccess(target, invokedElement); |
| } |
| } |
| } |
| |
| void _addFixFromBuilder(ChangeBuilder builder, FixKind kind, |
| {List<Object> args, bool importsOnly = false}) { |
| if (builder == null) return; |
| var change = builder.sourceChange; |
| if (change.edits.isEmpty && !importsOnly) { |
| return; |
| } |
| change.id = kind.id; |
| change.message = formatList(kind.message, args); |
| fixes.add(Fix(kind, change)); |
| } |
| |
| Future<void> _addFromProducers() async { |
| var context = CorrectionProducerContext( |
| diagnostic: error, |
| resolvedResult: resolvedResult, |
| selectionOffset: errorOffset, |
| selectionLength: errorLength, |
| workspace: workspace, |
| ); |
| |
| var setupSuccess = context.setupCompute(); |
| if (!setupSuccess) { |
| return; |
| } |
| |
| Future<void> compute(CorrectionProducer producer) async { |
| producer.configure(context); |
| var builder = _newDartChangeBuilder(); |
| await producer.compute(builder); |
| _addFixFromBuilder(builder, producer.fixKind, |
| args: producer.fixArguments); |
| } |
| |
| var errorCode = error.errorCode; |
| if (errorCode is LintCode) { |
| var generators = lintProducerMap[errorCode.name]; |
| if (generators != null) { |
| for (var generator in generators) { |
| await compute(generator()); |
| } |
| } |
| } else { |
| var generators = nonLintProducerMap[errorCode]; |
| if (generators != null) { |
| for (var generator in generators) { |
| 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); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Prepares proposal for creating function corresponding to the given |
| /// [FunctionType]. |
| Future<DartChangeBuilder> _addProposal_createFunction( |
| FunctionType functionType, |
| String name, |
| String targetFile, |
| int insertOffset, |
| bool isStatic, |
| String prefix, |
| String sourcePrefix, |
| String sourceSuffix, |
| Element target) async { |
| // build method source |
| var changeBuilder = _newDartChangeBuilder(); |
| await changeBuilder.addFileEdit(targetFile, (DartFileEditBuilder builder) { |
| builder.addInsertion(insertOffset, (DartEditBuilder builder) { |
| builder.write(sourcePrefix); |
| builder.write(prefix); |
| // may be static |
| if (isStatic) { |
| builder.write('static '); |
| } |
| // append return type |
| if (builder.writeType(functionType.returnType, |
| groupName: 'RETURN_TYPE')) { |
| builder.write(' '); |
| } |
| // append name |
| builder.addLinkedEdit('NAME', (DartLinkedEditBuilder builder) { |
| builder.write(name); |
| }); |
| // append parameters |
| builder.writeParameters(functionType.parameters); |
| // close method |
| builder.write(' {$eol$prefix}'); |
| builder.write(sourceSuffix); |
| }); |
| if (targetFile == file) { |
| builder.addLinkedPosition(range.node(node), 'NAME'); |
| } |
| }); |
| return changeBuilder; |
| } |
| |
| /// Adds proposal for creating method corresponding to the given |
| /// [FunctionType] in the given [ClassElement]. |
| Future<void> _addProposal_createFunction_function( |
| FunctionType functionType) async { |
| var name = (node as SimpleIdentifier).name; |
| // prepare environment |
| var insertOffset = unit.end; |
| // prepare prefix |
| var prefix = ''; |
| var sourcePrefix = '$eol'; |
| var sourceSuffix = eol; |
| var changeBuilder = await _addProposal_createFunction( |
| functionType, |
| name, |
| file, |
| insertOffset, |
| false, |
| prefix, |
| sourcePrefix, |
| sourceSuffix, |
| unit.declaredElement); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_FUNCTION, |
| args: [name]); |
| } |
| |
| /// Adds proposal for creating method corresponding to the given |
| /// [FunctionType] in the given [ClassElement]. |
| Future<void> _addProposal_createFunction_method( |
| ClassElement targetClassElement, FunctionType functionType) async { |
| var name = (node as SimpleIdentifier).name; |
| // prepare environment |
| var targetSource = targetClassElement.source; |
| // prepare insert offset |
| var targetNode = await _getClassDeclaration(targetClassElement); |
| if (targetNode == null) { |
| return; |
| } |
| var insertOffset = targetNode.end - 1; |
| // prepare prefix |
| var prefix = ' '; |
| String sourcePrefix; |
| if (targetNode.members.isEmpty) { |
| sourcePrefix = ''; |
| } else { |
| sourcePrefix = eol; |
| } |
| var sourceSuffix = eol; |
| var changeBuilder = await _addProposal_createFunction( |
| functionType, |
| name, |
| targetSource.fullName, |
| insertOffset, |
| _inStaticContext(), |
| prefix, |
| sourcePrefix, |
| sourceSuffix, |
| targetClassElement); |
| _addFixFromBuilder(changeBuilder, DartFixKind.CREATE_METHOD, args: [name]); |
| } |
| |
| /// Return the class, enum or mixin declaration for the given [element]. |
| Future<ClassOrMixinDeclaration> _getClassDeclaration( |
| ClassElement element) async { |
| var result = await sessionHelper.getElementDeclaration(element); |
| if (result.node is ClassOrMixinDeclaration) { |
| return result.node; |
| } |
| return null; |
| } |
| |
| /// Return the string to display as the name of the given constructor in a |
| /// proposal name. |
| String _getConstructorProposalName(ConstructorElement constructor) { |
| var buffer = StringBuffer(); |
| buffer.write('super'); |
| var constructorName = constructor.displayName; |
| if (constructorName.isNotEmpty) { |
| buffer.write('.'); |
| buffer.write(constructorName); |
| } |
| buffer.write('(...)'); |
| return buffer.toString(); |
| } |
| |
| /// Return the extension declaration for the given [element]. |
| Future<ExtensionDeclaration> _getExtensionDeclaration( |
| ExtensionElement element) async { |
| var result = await sessionHelper.getElementDeclaration(element); |
| if (result.node is ExtensionDeclaration) { |
| return result.node; |
| } |
| return null; |
| } |
| |
| /// Return the relative uri from the passed [library] to the given [path]. |
| /// If the [path] is not in the LibraryElement, `null` is returned. |
| String _getRelativeURIFromLibrary(LibraryElement library, String path) { |
| var librarySource = library?.librarySource; |
| if (librarySource == null) { |
| return null; |
| } |
| var pathCtx = resourceProvider.pathContext; |
| var libraryDirectory = pathCtx.dirname(librarySource.fullName); |
| var sourceDirectory = pathCtx.dirname(path); |
| if (pathCtx.isWithin(libraryDirectory, path) || |
| pathCtx.isWithin(sourceDirectory, libraryDirectory)) { |
| var relativeFile = pathCtx.relative(path, from: libraryDirectory); |
| return pathCtx.split(relativeFile).join('/'); |
| } |
| return null; |
| } |
| |
| /// Returns an expected [DartType] of [expression], may be `null` if cannot be |
| /// inferred. |
| DartType _inferUndefinedExpressionType(Expression expression) { |
| var parent = expression.parent; |
| // myFunction(); |
| if (parent is ExpressionStatement) { |
| if (expression is MethodInvocation) { |
| return VoidTypeImpl.instance; |
| } |
| } |
| // return myFunction(); |
| if (parent is ReturnStatement) { |
| var executable = getEnclosingExecutableElement(expression); |
| return executable?.returnType; |
| } |
| // int v = myFunction(); |
| if (parent is VariableDeclaration) { |
| var variableDeclaration = parent; |
| if (variableDeclaration.initializer == expression) { |
| var variableElement = variableDeclaration.declaredElement; |
| if (variableElement != null) { |
| return variableElement.type; |
| } |
| } |
| } |
| // myField = 42; |
| if (parent is AssignmentExpression) { |
| var assignment = parent; |
| if (assignment.leftHandSide == expression) { |
| var rhs = assignment.rightHandSide; |
| if (rhs != null) { |
| return rhs.staticType; |
| } |
| } |
| } |
| // v = myFunction(); |
| if (parent is AssignmentExpression) { |
| var assignment = parent; |
| if (assignment.rightHandSide == expression) { |
| if (assignment.operator.type == TokenType.EQ) { |
| // v = myFunction(); |
| var lhs = assignment.leftHandSide; |
| if (lhs != null) { |
| return lhs.staticType; |
| } |
| } else { |
| // v += myFunction(); |
| var method = assignment.staticElement; |
| if (method != null) { |
| var parameters = method.parameters; |
| if (parameters.length == 1) { |
| return parameters[0].type; |
| } |
| } |
| } |
| } |
| } |
| // v + myFunction(); |
| if (parent is BinaryExpression) { |
| var binary = parent; |
| var method = binary.staticElement; |
| if (method != null) { |
| if (binary.rightOperand == expression) { |
| var parameters = method.parameters; |
| return parameters.length == 1 ? parameters[0].type : null; |
| } |
| } |
| } |
| // foo( myFunction() ); |
| if (parent is ArgumentList) { |
| var parameter = expression.staticParameterElement; |
| return parameter?.type; |
| } |
| // bool |
| { |
| // assert( myFunction() ); |
| if (parent is AssertStatement) { |
| var statement = parent; |
| if (statement.condition == expression) { |
| return coreTypeBool; |
| } |
| } |
| // if ( myFunction() ) {} |
| if (parent is IfStatement) { |
| var statement = parent; |
| if (statement.condition == expression) { |
| return coreTypeBool; |
| } |
| } |
| // while ( myFunction() ) {} |
| if (parent is WhileStatement) { |
| var statement = parent; |
| if (statement.condition == expression) { |
| return coreTypeBool; |
| } |
| } |
| // do {} while ( myFunction() ); |
| if (parent is DoStatement) { |
| var statement = parent; |
| if (statement.condition == expression) { |
| return coreTypeBool; |
| } |
| } |
| // !myFunction() |
| if (parent is PrefixExpression) { |
| var prefixExpression = parent; |
| if (prefixExpression.operator.type == TokenType.BANG) { |
| return coreTypeBool; |
| } |
| } |
| // binary expression '&&' or '||' |
| if (parent is BinaryExpression) { |
| var binaryExpression = parent; |
| var operatorType = binaryExpression.operator.type; |
| if (operatorType == TokenType.AMPERSAND_AMPERSAND || |
| operatorType == TokenType.BAR_BAR) { |
| return coreTypeBool; |
| } |
| } |
| } |
| // we don't know |
| return null; |
| } |
| |
| /// Returns `true` if [node] is in static context. |
| bool _inStaticContext() { |
| // constructor initializer cannot reference "this" |
| if (node.thisOrAncestorOfType<ConstructorInitializer>() != null) { |
| return true; |
| } |
| // field initializer cannot reference "this" |
| if (node.thisOrAncestorOfType<FieldDeclaration>() != null) { |
| return true; |
| } |
| // static method |
| var method = node.thisOrAncestorOfType<MethodDeclaration>(); |
| return method != null && method.isStatic; |
| } |
| |
| bool _isAwaitNode() { |
| var node = this.node; |
| return node is SimpleIdentifier && node.name == 'await'; |
| } |
| |
| bool _isLibSrcPath(String path) { |
| var parts = resourceProvider.pathContext.split(path); |
| for (var i = 0; i < parts.length - 2; i++) { |
| if (parts[i] == 'lib' && parts[i + 1] == 'src') { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| DartChangeBuilder _newDartChangeBuilder() { |
| return DartChangeBuilderImpl.forWorkspace(context.workspace); |
| } |
| |
| /// Removes any [ParenthesizedExpression] enclosing [expr]. |
| /// |
| /// [exprPrecedence] - the effective precedence of [expr]. |
| void _removeEnclosingParentheses( |
| DartFileEditBuilder builder, Expression expr, Precedence exprPrecedence) { |
| while (expr.parent is ParenthesizedExpression) { |
| var parenthesized = expr.parent as ParenthesizedExpression; |
| if (getExpressionParentPrecedence(parenthesized) > exprPrecedence) { |
| break; |
| } |
| builder.addDeletion(range.token(parenthesized.leftParenthesis)); |
| builder.addDeletion(range.token(parenthesized.rightParenthesis)); |
| expr = parenthesized; |
| } |
| } |
| |
| void _updateFinderWithClassMembers( |
| _ClosestElementFinder finder, ClassElement clazz) { |
| if (clazz != null) { |
| var members = getMembers(clazz); |
| finder._updateList(members); |
| } |
| } |
| |
| void _updateFinderWithExtensionMembers( |
| _ClosestElementFinder finder, ExtensionElement element) { |
| if (element != null) { |
| finder._updateList(getExtensionMembers(element)); |
| } |
| } |
| |
| static ClassElement _getTargetClassElement(Expression target) { |
| var type = target.staticType; |
| if (type is InterfaceType) { |
| return type.element; |
| } else if (target is Identifier) { |
| var element = target.staticElement; |
| if (element is ClassElement) { |
| return element; |
| } |
| } |
| return null; |
| } |
| |
| static bool _isNameOfType(String name) { |
| if (name.isEmpty) { |
| return false; |
| } |
| var firstLetter = name.substring(0, 1); |
| if (firstLetter.toUpperCase() != firstLetter) { |
| return false; |
| } |
| return true; |
| } |
| |
| /// Return `true` if the given [node] is in a location where an implicit |
| /// constructor invocation would be allowed. |
| static bool _mayBeImplicitConstructor(AstNode node) { |
| if (node is SimpleIdentifier) { |
| var parent = node.parent; |
| if (parent is MethodInvocation) { |
| return parent.realTarget == null; |
| } |
| } |
| return false; |
| } |
| |
| /// Returns `true` if [node] is a type name. |
| static bool _mayBeTypeIdentifier(AstNode node) { |
| if (node is SimpleIdentifier) { |
| var parent = node.parent; |
| if (parent is TypeName) { |
| return true; |
| } |
| return _isNameOfType(node.name); |
| } |
| return false; |
| } |
| } |
| |
| /// Helper for finding [Element] with name closest to the given. |
| class _ClosestElementFinder { |
| final String _targetName; |
| final ElementPredicate _predicate; |
| |
| Element _element; |
| int _distance; |
| |
| _ClosestElementFinder(this._targetName, this._predicate, this._distance); |
| |
| void _update(Element element) { |
| if (_predicate(element)) { |
| var memberDistance = levenshtein(element.name, _targetName, _distance); |
| if (memberDistance < _distance) { |
| _element = element; |
| _distance = memberDistance; |
| } |
| } |
| } |
| |
| void _updateList(Iterable<Element> elements) { |
| for (var element in elements) { |
| _update(element); |
| } |
| } |
| } |
| |
| /// [ExecutableElement], its parameters, and operations on them. |
| class _ExecutableParameters { |
| final AnalysisSessionHelper sessionHelper; |
| final ExecutableElement executable; |
| |
| final List<ParameterElement> required = []; |
| final List<ParameterElement> optionalPositional = []; |
| final List<ParameterElement> named = []; |
| |
| factory _ExecutableParameters( |
| AnalysisSessionHelper sessionHelper, AstNode invocation) { |
| Element element; |
| // This doesn't handle FunctionExpressionInvocation. |
| if (invocation is Annotation) { |
| element = invocation.element; |
| } else if (invocation is InstanceCreationExpression) { |
| element = invocation.staticElement; |
| } else if (invocation is MethodInvocation) { |
| element = invocation.methodName.staticElement; |
| } else if (invocation is ConstructorReferenceNode) { |
| element = invocation.staticElement; |
| } |
| if (element is ExecutableElement && !element.isSynthetic) { |
| return _ExecutableParameters._(sessionHelper, element); |
| } else { |
| return null; |
| } |
| } |
| |
| _ExecutableParameters._(this.sessionHelper, this.executable) { |
| for (var parameter in executable.parameters) { |
| if (parameter.isRequiredPositional) { |
| required.add(parameter); |
| } else if (parameter.isOptionalPositional) { |
| optionalPositional.add(parameter); |
| } else if (parameter.isNamed) { |
| named.add(parameter); |
| } |
| } |
| } |
| |
| String get file => executable.source.fullName; |
| |
| List<String> get namedNames { |
| return named.map((parameter) => parameter.name).toList(); |
| } |
| |
| /// Return the [FormalParameterList] of the [executable], or `null` is cannot |
| /// be found. |
| Future<FormalParameterList> getParameterList() async { |
| var result = await sessionHelper.getElementDeclaration(executable); |
| var targetDeclaration = result.node; |
| if (targetDeclaration is ConstructorDeclaration) { |
| return targetDeclaration.parameters; |
| } else if (targetDeclaration is FunctionDeclaration) { |
| var function = targetDeclaration.functionExpression; |
| return function.parameters; |
| } else if (targetDeclaration is MethodDeclaration) { |
| return targetDeclaration.parameters; |
| } |
| return null; |
| } |
| |
| /// Return the [FormalParameter] of the [element] in [FormalParameterList], |
| /// or `null` is cannot be found. |
| Future<FormalParameter> getParameterNode(ParameterElement element) async { |
| var result = await sessionHelper.getElementDeclaration(element); |
| var declaration = result.node; |
| for (var node = declaration; node != null; node = node.parent) { |
| if (node is FormalParameter && node.parent is FormalParameterList) { |
| return node; |
| } |
| } |
| return null; |
| } |
| } |