diff --git a/pkg/analysis_server/lib/src/services/correction/dart/add_call_super.dart b/pkg/analysis_server/lib/src/services/correction/dart/add_call_super.dart new file mode 100644 index 00000000000..8b6fb5086c7 --- /dev/null +++ b/pkg/analysis_server/lib/src/services/correction/dart/add_call_super.dart @@ -0,0 +1,98 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart'; +import 'package:analysis_server/src/services/correction/fix.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/src/dart/element/inheritance_manager3.dart'; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:analyzer_plugin/utilities/range_factory.dart'; +import 'package:collection/collection.dart'; + +class AddCallSuper extends CorrectionProducer { + var _addition = ''; + + @override + // Adding as the first statement is not predictably the correct action. + bool get canBeAppliedInBulk => false; + + @override + // Adding as the first statement is not predictably the correct action. + bool get canBeAppliedToFile => false; + + @override + List get fixArguments => [_addition]; + + @override + FixKind get fixKind => DartFixKind.ADD_CALL_SUPER; + + @override + Future compute(ChangeBuilder builder) async { + var node = this.node; + if (node is! SimpleIdentifier) return; + var methodDeclaration = node.thisOrAncestorOfType(); + if (methodDeclaration == null) return; + var classElement = methodDeclaration + .thisOrAncestorOfType() + ?.declaredElement; + if (classElement == null) return; + + var name = Name(classElement.library.source.uri, node.name); + var overridden = InheritanceManager3().getInherited2(classElement, name); + if (overridden == null) return; + var overriddenParameters = overridden.parameters.map((p) => p.name); + + var body = methodDeclaration.body; + var parameters = methodDeclaration.parameters?.parameters; + var argumentList = parameters + ?.map((p) { + var name = p.identifier?.name; + if (overriddenParameters.contains(name)) { + return p.isNamed ? '$name: $name' : name; + } + return null; + }) + .whereNotNull() + .join(', ') ?? + ''; + + _addition = '${node.name}($argumentList)'; + + if (body is BlockFunctionBody) { + await _block(builder, body); + } else if (body is ExpressionFunctionBody) { + await _expression(builder, body); + } + } + + Future _block(ChangeBuilder builder, BlockFunctionBody body) async { + var location = utils.prepareNewStatementLocation(body.block, true); + + await builder.addDartFileEdit(file, (builder) { + builder.addInsertion(location.offset, (builder) { + builder.write(location.prefix); + builder.write('super.$_addition;'); + builder.write(location.suffix); + }); + }); + } + + Future _expression( + ChangeBuilder builder, ExpressionFunctionBody body) async { + var expression = body.expression; + var semicolon = body.semicolon; + var prefix = utils.getLinePrefix(expression.offset); + var prefixWithLine = eol + prefix + utils.getIndent(1); + + await builder.addDartFileEdit(file, (builder) { + builder.addSimpleReplacement( + range.startStart(body.functionDefinition, expression), + '{${prefixWithLine}super.$_addition;${prefixWithLine}return '); + + builder.addSimpleReplacement( + range.endEnd(expression, semicolon ?? expression), ';$eol$prefix}'); + }); + } +} diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_for_final_fields.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_for_final_fields.dart index 90f757ca0d1..98d9df530ba 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_for_final_fields.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_for_final_fields.dart @@ -86,7 +86,7 @@ class CreateConstructorForFinalFields extends CorrectionProducer { Future _withoutSuperParameters( ChangeBuilder builder, - ClassMemberLocation targetLocation, + InsertionLocation targetLocation, String className, ClassElement keyClass, List variableLists) async { @@ -117,7 +117,7 @@ class CreateConstructorForFinalFields extends CorrectionProducer { Future _withSuperParameters( ChangeBuilder builder, - ClassMemberLocation targetLocation, + InsertionLocation targetLocation, String className, List variableLists) async { await builder.addDartFileEdit(file, (builder) { diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_super.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_super.dart index 2c94494570d..5c5eccbbfb6 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_super.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/create_constructor_super.dart @@ -47,7 +47,7 @@ class _CreateConstructor extends CorrectionProducer { final ConstructorElement _constructor; /// An indication of where the new constructor should be added. - final ClassMemberLocation _targetLocation; + final InsertionLocation _targetLocation; /// The name of the class in which the constructor will be added. final String _targetClassName; diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index ae9517c539e..4c4210b7514 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -1340,8 +1340,7 @@ HintCode.MIXIN_ON_SEALED_CLASS: HintCode.MUST_BE_IMMUTABLE: status: needsEvaluation HintCode.MUST_CALL_SUPER: - status: needsFix - issue: https://github.com/dart-lang/sdk/issues/33985 + status: hasFix HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR: status: needsEvaluation HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW: diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart index cd161c2baea..9a729ee7752 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix.dart @@ -93,6 +93,11 @@ class DartFixKind { DartFixKindPriority.DEFAULT, 'Add cast', ); + static const ADD_CALL_SUPER = FixKind( + 'dart.fix.add.callSuper', + DartFixKindPriority.DEFAULT, + "Add 'super.{0}'", + ); static const ADD_CONST = FixKind( 'dart.fix.add.const', DartFixKindPriority.DEFAULT, diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart index ed60cb1c45a..dcb96bf9c16 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart @@ -8,6 +8,7 @@ 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_call_super.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_enum_constant.dart'; @@ -1234,6 +1235,9 @@ class FixProcessor extends BaseProcessor { HintCode.MISSING_RETURN: [ AddAsync.missingReturn, ], + HintCode.MUST_CALL_SUPER: [ + AddCallSuper.new, + ], HintCode.NULLABLE_TYPE_IN_CATCH_CLAUSE: [ RemoveQuestionMark.new, ], diff --git a/pkg/analysis_server/lib/src/services/correction/util.dart b/pkg/analysis_server/lib/src/services/correction/util.dart index fd87a5d6f0b..be9d2c076f7 100644 --- a/pkg/analysis_server/lib/src/services/correction/util.dart +++ b/pkg/analysis_server/lib/src/services/correction/util.dart @@ -524,15 +524,6 @@ class CancelCorrectionException { CancelCorrectionException({this.exception}); } -/// Describes the location for a newly created [ClassMember]. -class ClassMemberLocation { - final String prefix; - final int offset; - final String suffix; - - ClassMemberLocation(this.prefix, this.offset, this.suffix); -} - class CorrectionUtils { final CompilationUnit unit; final LibraryElement _library; @@ -979,7 +970,7 @@ class CorrectionUtils { return TokenUtils.getTokens(trimmedText, unit.featureSet).isEmpty; } - ClassMemberLocation newCaseClauseAtEndLocation(SwitchStatement statement) { + InsertionLocation newCaseClauseAtEndLocation(SwitchStatement statement) { var blockStartLine = getLineThis(statement.leftBracket.offset); var blockEndLine = getLineThis(statement.end); var offset = blockEndLine; @@ -991,10 +982,10 @@ class CorrectionUtils { offset = statement.leftBracket.end; suffix = getLinePrefix(statement.offset); } - return ClassMemberLocation(prefix, offset, suffix); + return InsertionLocation(prefix, offset, suffix); } - ClassMemberLocation? prepareEnumNewConstructorLocation( + InsertionLocation? prepareEnumNewConstructorLocation( EnumDeclaration enumDeclaration, ) { var indent = getIndent(1); @@ -1003,7 +994,7 @@ class CorrectionUtils { .where((e) => e is FieldDeclaration || e is ConstructorDeclaration) .lastOrNull; if (targetMember != null) { - return ClassMemberLocation( + return InsertionLocation( endOfLine + endOfLine + indent, targetMember.end, '', @@ -1012,7 +1003,7 @@ class CorrectionUtils { var semicolon = enumDeclaration.semicolon; if (semicolon != null) { - return ClassMemberLocation( + return InsertionLocation( endOfLine + endOfLine + indent, semicolon.end, '', @@ -1020,14 +1011,14 @@ class CorrectionUtils { } var lastConstant = enumDeclaration.constants.last; - return ClassMemberLocation( + return InsertionLocation( ';$endOfLine$endOfLine$indent', lastConstant.end, '', ); } - ClassMemberLocation? prepareNewClassMemberLocation( + InsertionLocation? prepareNewClassMemberLocation( CompilationUnitMember declaration, bool Function(ClassMember existingMember) shouldSkip) { var indent = getIndent(1); @@ -1046,18 +1037,18 @@ class CorrectionUtils { } // After the last target member. if (targetMember != null) { - return ClassMemberLocation( + return InsertionLocation( endOfLine + endOfLine + indent, targetMember.end, ''); } // At the beginning of the class. var suffix = members.isNotEmpty || isClassWithEmptyBody(declaration) ? endOfLine : ''; - return ClassMemberLocation( + return InsertionLocation( endOfLine + indent, _getLeftBracket(declaration)!.end, suffix); } - ClassMemberLocation? prepareNewConstructorLocation( + InsertionLocation? prepareNewConstructorLocation( AnalysisSession session, ClassDeclaration classDeclaration) { final sortConstructorsFirst = session.analysisContext.analysisOptions .isLintEnabled(LintNames.sort_constructors_first); @@ -1070,13 +1061,13 @@ class CorrectionUtils { return prepareNewClassMemberLocation(classDeclaration, shouldSkip); } - ClassMemberLocation? prepareNewFieldLocation( + InsertionLocation? prepareNewFieldLocation( CompilationUnitMember declaration) { return prepareNewClassMemberLocation( declaration, (member) => member is FieldDeclaration); } - ClassMemberLocation? prepareNewGetterLocation( + InsertionLocation? prepareNewGetterLocation( CompilationUnitMember declaration) { return prepareNewClassMemberLocation( declaration, @@ -1086,7 +1077,7 @@ class CorrectionUtils { member is MethodDeclaration && member.isGetter); } - ClassMemberLocation? prepareNewMethodLocation( + InsertionLocation? prepareNewMethodLocation( CompilationUnitMember declaration) { return prepareNewClassMemberLocation( declaration, @@ -1096,6 +1087,30 @@ class CorrectionUtils { member is MethodDeclaration); } + /// Return the location of a new statement in the given [block], as the + /// first statement if [first] is `true`, or the last one if `false`. + InsertionLocation prepareNewStatementLocation(Block block, bool first) { + var statements = block.statements; + var empty = statements.isEmpty; + var last = empty || first ? block.leftBracket : statements.last; + + var linePrefix = getLinePrefix(last.offset); + var indent = getIndent(1); + String prefix; + String suffix; + if (empty) { + prefix = endOfLine + linePrefix + indent; + suffix = endOfLine + linePrefix; + } else if (first) { + prefix = endOfLine + linePrefix + indent; + suffix = ''; + } else { + prefix = endOfLine + linePrefix; + suffix = ''; + } + return InsertionLocation(prefix, last.end, suffix); + } + /// Returns the source with indentation changed from [oldIndent] to /// [newIndent], keeping indentation of lines relative to each other. String replaceSourceIndent( @@ -1381,6 +1396,14 @@ class CorrectionUtils_InsertDesc { String suffix = ''; } +class InsertionLocation { + final String prefix; + final int offset; + final String suffix; + + InsertionLocation(this.prefix, this.offset, this.suffix); +} + /// Utilities to work with [Token]s. class TokenUtils { static List getNodeTokens(AstNode node) { diff --git a/pkg/analysis_server/test/src/services/correction/fix/add_call_super_test.dart b/pkg/analysis_server/test/src/services/correction/fix/add_call_super_test.dart new file mode 100644 index 00000000000..af4ed50037c --- /dev/null +++ b/pkg/analysis_server/test/src/services/correction/fix/add_call_super_test.dart @@ -0,0 +1,276 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analysis_server/src/services/correction/fix.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'fix_processor.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AddCallSuperTest); + }); +} + +@reflectiveTest +class AddCallSuperTest extends FixProcessorTest { + @override + FixKind get kind => DartFixKind.ADD_CALL_SUPER; + + @override + void setUp() { + super.setUp(); + writeTestPackageConfig(meta: true); + } + + Future test_body() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + void a() {} +} +class B extends A { + @override + void a() {} +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + void a() {} +} +class B extends A { + @override + void a() { + super.a(); + } +} +''', matchFixMessage: "Add 'super.a()'"); + } + + Future test_body_added_parameters() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + void m(int x) {} +} +class B extends A { + @override + void m(int x, [int? y]) {} +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + void m(int x) {} +} +class B extends A { + @override + void m(int x, [int? y]) { + super.m(x); + } +} +''', matchFixMessage: "Add 'super.m(x)'"); + } + + Future test_body_optional() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, {int y = 0}) { + return x = y; + } +} +class B extends A { + @override + int a(int x, {int y = 0}) { + return x = y; + } +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, {int y = 0}) { + return x = y; + } +} +class B extends A { + @override + int a(int x, {int y = 0}) { + super.a(x, y: y); + return x = y; + } +} +''', matchFixMessage: "Add 'super.a(x, y: y)'"); + } + + Future test_body_parameters() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + void a(int i) {} +} +class B extends A { + @override + void a(int i) {} +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + void a(int i) {} +} +class B extends A { + @override + void a(int i) { + super.a(i); + } +} +''', matchFixMessage: "Add 'super.a(i)'"); + } + + Future test_body_required() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, {required int y}) { + return x = y; + } +} +class B extends A { + @override + int a(int x, {required int y}) { + return x = y; + } +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, {required int y}) { + return x = y; + } +} +class B extends A { + @override + int a(int x, {required int y}) { + super.a(x, y: y); + return x = y; + } +} +''', matchFixMessage: "Add 'super.a(x, y: y)'"); + } + + Future test_expression_async() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + Future m() async => 3; +} +class B extends A { + @override + Future m() async => 3; +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + Future m() async => 3; +} +class B extends A { + @override + Future m() async { + super.m(); + return 3; + } +} +''', matchFixMessage: "Add 'super.m()'"); + } + + Future test_expression_parameters() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, int y) => x + y; +} +class B extends A { + @override + int a(int x, int y) => x + y; +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, int y) => x + y; +} +class B extends A { + @override + int a(int x, int y) { + super.a(x, y); + return x + y; + } +} +''', matchFixMessage: "Add 'super.a(x, y)'"); + } + + Future test_expression_positional() async { + await resolveTestCode(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, [int y = 0]) => x + y; +} +class B extends A { + @override + int a(int x, [int y = 0]) => x + y; +} +'''); + await assertHasFix(''' +import 'package:meta/meta.dart'; + +class A { + @mustCallSuper + int a(int x, [int y = 0]) => x + y; +} +class B extends A { + @override + int a(int x, [int y = 0]) { + super.a(x, y); + return x + y; + } +} +''', matchFixMessage: "Add 'super.a(x, y)'"); + } +} diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart index efbc6aad6ff..d383acf3f4a 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart @@ -6,6 +6,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'add_async_test.dart' as add_async; import 'add_await_test.dart' as add_await; +import 'add_call_super_test.dart' as add_call_super; import 'add_const_test.dart' as add_const; import 'add_curly_braces_test.dart' as add_curly_braces; import 'add_diagnostic_property_reference_test.dart' @@ -237,6 +238,7 @@ void main() { defineReflectiveSuite(() { add_async.main(); add_await.main(); + add_call_super.main(); add_const.main(); add_curly_braces.main(); add_diagnostic_property_reference.main();