mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 15:57:17 +00:00
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:
parent
13fb73dfec
commit
bd4ae9b309
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1261,6 +1261,7 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
|
|||
CompileTimeErrorCode.UNDEFINED_GETTER: [
|
||||
ChangeTo.getterOrSetter,
|
||||
CreateClass.new,
|
||||
CreateExtensionGetter.new,
|
||||
CreateField.new,
|
||||
CreateGetter.new,
|
||||
CreateLocalVariable.new,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue