Add fix for MUST_CALL_SUPER

Fixes #33985

Change-Id: I5ff66bc01dde162979a21ebe8374398c2ffea783
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/240846
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Ahmed Ashour 2022-04-23 04:57:51 +00:00 committed by Commit Bot
parent c94b26e5b1
commit 1f27a7dca9
9 changed files with 434 additions and 27 deletions

View file

@ -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<Object> get fixArguments => [_addition];
@override
FixKind get fixKind => DartFixKind.ADD_CALL_SUPER;
@override
Future<void> compute(ChangeBuilder builder) async {
var node = this.node;
if (node is! SimpleIdentifier) return;
var methodDeclaration = node.thisOrAncestorOfType<MethodDeclaration>();
if (methodDeclaration == null) return;
var classElement = methodDeclaration
.thisOrAncestorOfType<ClassDeclaration>()
?.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<void> _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<void> _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}');
});
}
}

View file

@ -86,7 +86,7 @@ class CreateConstructorForFinalFields extends CorrectionProducer {
Future<void> _withoutSuperParameters(
ChangeBuilder builder,
ClassMemberLocation targetLocation,
InsertionLocation targetLocation,
String className,
ClassElement keyClass,
List<VariableDeclarationList> variableLists) async {
@ -117,7 +117,7 @@ class CreateConstructorForFinalFields extends CorrectionProducer {
Future<void> _withSuperParameters(
ChangeBuilder builder,
ClassMemberLocation targetLocation,
InsertionLocation targetLocation,
String className,
List<VariableDeclarationList> variableLists) async {
await builder.addDartFileEdit(file, (builder) {

View file

@ -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;

View file

@ -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:

View file

@ -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,

View file

@ -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,
],

View file

@ -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<Token> getNodeTokens(AstNode node) {

View file

@ -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<void> 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<void> 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<void> 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<void> 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<void> 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<void> test_expression_async() async {
await resolveTestCode('''
import 'package:meta/meta.dart';
class A {
@mustCallSuper
Future<int> m() async => 3;
}
class B extends A {
@override
Future<int> m() async => 3;
}
''');
await assertHasFix('''
import 'package:meta/meta.dart';
class A {
@mustCallSuper
Future<int> m() async => 3;
}
class B extends A {
@override
Future<int> m() async {
super.m();
return 3;
}
}
''', matchFixMessage: "Add 'super.m()'");
}
Future<void> 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<void> 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)'");
}
}

View file

@ -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();