QuickFix. Issue 55805. Create extension getter.

Bug: https://github.com/dart-lang/sdk/issues/55805
Change-Id: I52f1cdf036e17dda4e91da98beef19184f68a965
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/368502
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2024-05-30 01:37:21 +00:00 committed by Commit Queue
parent 13fb73dfec
commit bd4ae9b309
5 changed files with 341 additions and 0 deletions

View file

@ -10,10 +10,123 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/resolver/applicable_extensions.dart';
import 'package:analyzer/src/utilities/extensions/ast.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';
import 'package:meta/meta.dart';
class CreateExtensionGetter extends ResolvedCorrectionProducer {
String _getterName = '';
@override
CorrectionApplicability get applicability {
// Not predictably the correct action.
return CorrectionApplicability.singleLocation;
}
@override
List<String> get fixArguments => [_getterName];
@override
FixKind get fixKind => DartFixKind.CREATE_EXTENSION_GETTER;
@override
Future<void> compute(ChangeBuilder builder) async {
var nameNode = node;
if (nameNode is! SimpleIdentifier) {
return;
}
if (!nameNode.inGetterContext()) {
return;
}
_getterName = nameNode.name;
// prepare target
Expression? target;
switch (nameNode.parent) {
case PrefixedIdentifier prefixedIdentifier:
if (prefixedIdentifier.identifier == nameNode) {
target = prefixedIdentifier.prefix;
}
case PropertyAccess propertyAccess:
if (propertyAccess.propertyName == nameNode) {
target = propertyAccess.target;
}
}
if (target == null) {
return;
}
// We need the type for the extension.
var targetType = target.staticType;
if (targetType == null ||
targetType is DynamicType ||
targetType is InvalidType) {
return;
}
// Try to find the type of the field.
var fieldTypeNode = climbPropertyAccess(nameNode);
var fieldType = inferUndefinedExpressionType(fieldTypeNode);
void writeGetter(DartEditBuilder builder) {
if (fieldType != null) {
builder.writeType(fieldType);
builder.write(' ');
}
builder.write('get $_getterName => ');
builder.addLinkedEdit('VALUE', (builder) {
builder.write('null');
});
builder.write(';');
}
// Try to add to an existing extension.
for (var existingExtension in unitResult.unit.declarations) {
if (existingExtension is ExtensionDeclaration) {
var element = existingExtension.declaredElement!;
var instantiated = [element].applicableTo(
targetLibrary: libraryElement,
targetType: targetType,
strictCasts: true,
);
if (instantiated.isNotEmpty) {
await builder.addDartFileEdit(file, (builder) {
builder.insertGetter(existingExtension, (builder) {
writeGetter(builder);
});
});
return;
}
}
}
// The new extension should be added after it.
var enclosingUnitChild = nameNode.enclosingUnitChild;
if (enclosingUnitChild == null) {
return;
}
// Add a new extension.
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(enclosingUnitChild.end, (builder) {
builder.writeln();
builder.writeln();
builder.write('extension on ');
builder.writeType(targetType);
builder.writeln(' {');
builder.write(' ');
writeGetter(builder);
builder.writeln();
builder.write('}');
});
});
}
}
/// Shared implementation that identifies what getter should be added,
/// but delegates to the subtypes to produce the fix code.
abstract class CreateFieldOrGetter extends ResolvedCorrectionProducer {

View file

@ -681,6 +681,11 @@ class DartFixKind {
DartFixKindPriority.DEFAULT,
'Create constructor to call {0}',
);
static const CREATE_EXTENSION_GETTER = FixKind(
'dart.fix.create.extension.getter',
DartFixKindPriority.DEFAULT - 20,
"Create extension getter '{0}'",
);
static const CREATE_FIELD = FixKind(
'dart.fix.create.field',
49,

View file

@ -1261,6 +1261,7 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
CompileTimeErrorCode.UNDEFINED_GETTER: [
ChangeTo.getterOrSetter,
CreateClass.new,
CreateExtensionGetter.new,
CreateField.new,
CreateGetter.new,
CreateLocalVariable.new,

View file

@ -10,11 +10,224 @@ import 'fix_processor.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(CreateExtensionGetterTest);
defineReflectiveTests(CreateGetterTest);
defineReflectiveTests(CreateGetterMixinTest);
});
}
@reflectiveTest
class CreateExtensionGetterTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.CREATE_EXTENSION_GETTER;
Future<void> test_contextType() async {
await resolveTestCode('''
void f() {
// ignore:unused_local_variable
int v = ''.test;
}
''');
await assertHasFix('''
void f() {
// ignore:unused_local_variable
int v = ''.test;
}
extension on String {
int get test => null;
}
''');
}
Future<void> test_contextType_no() async {
await resolveTestCode('''
void f() {
''.test;
}
''');
await assertHasFix('''
void f() {
''.test;
}
extension on String {
get test => null;
}
''');
assertLinkedGroup(change.linkedEditGroups[0], ['null']);
}
Future<void> test_existingExtension_contextType() async {
await resolveTestCode('''
void f() {
// ignore:unused_local_variable
int v = ''.test;
}
extension on String {}
''');
await assertHasFix('''
void f() {
// ignore:unused_local_variable
int v = ''.test;
}
extension on String {
int get test => null;
}
''');
}
Future<void> test_existingExtension_generic_matching() async {
await resolveTestCode('''
void f(List<int> a) {
a.test;
}
extension E<T> on Iterable<T> {}
''');
await assertHasFix('''
void f(List<int> a) {
a.test;
}
extension E<T> on Iterable<T> {
get test => null;
}
''');
}
Future<void> test_existingExtension_generic_notMatching() async {
await resolveTestCode('''
void f(List<int> a) {
a.test;
}
extension E<K, V> on Map<K, V> {}
''');
await assertHasFix('''
void f(List<int> a) {
a.test;
}
extension on List<int> {
get test => null;
}
extension E<K, V> on Map<K, V> {}
''');
}
Future<void> test_existingExtension_hasMethod() async {
await resolveTestCode('''
void f() {
''.test;
}
extension E on String {
// ignore:unused_element
void foo() {}
}
''');
await assertHasFix('''
void f() {
''.test;
}
extension E on String {
get test => null;
// ignore:unused_element
void foo() {}
}
''');
}
Future<void> test_existingExtension_notGeneric_matching() async {
await resolveTestCode('''
void f() {
''.test;
}
extension on String {}
''');
await assertHasFix('''
void f() {
''.test;
}
extension on String {
get test => null;
}
''');
}
Future<void> test_existingExtension_notGeneric_notMatching() async {
await resolveTestCode('''
void f() {
''.test;
}
extension on int {}
''');
await assertHasFix('''
void f() {
''.test;
}
extension on String {
get test => null;
}
extension on int {}
''');
}
Future<void> test_parent_nothing() async {
await resolveTestCode('''
void f() {
test;
}
''');
await assertNoFix();
}
Future<void> test_parent_prefixedIdentifier() async {
await resolveTestCode('''
void f(String a) {
a.test;
}
''');
await assertHasFix('''
void f(String a) {
a.test;
}
extension on String {
get test => null;
}
''');
}
Future<void> test_targetType_hasTypeArguments() async {
await resolveTestCode('''
void f(List<int> a) {
a.test;
}
''');
await assertHasFix('''
void f(List<int> a) {
a.test;
}
extension on List<int> {
get test => null;
}
''');
}
}
@reflectiveTest
class CreateGetterMixinTest extends FixProcessorTest {
@override

View file

@ -41,6 +41,15 @@ extension AstNodeExtension on AstNode {
return null;
}
AstNode? get enclosingUnitChild {
for (var node in withParents) {
if (node.parent is CompilationUnit) {
return node;
}
}
return null;
}
/// This node and all its parents.
Iterable<AstNode> get withParents sync* {
var current = this;