Code completion for record fields.

Change-Id: I03134b2c0c7fac2595c23b00f9d13b5e344d15a9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256961
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2022-08-30 23:11:08 +00:00 committed by Commit Bot
parent 3fd0d96e72
commit cfde826cc0
6 changed files with 194 additions and 1 deletions

View file

@ -937,6 +937,36 @@ class SuggestionBuilder {
);
}
void suggestRecordField({
required RecordTypeField field,
required String name,
}) {
final type = field.type;
final featureComputer = request.featureComputer;
final contextType =
featureComputer.contextTypeFeature(request.contextType, type);
final relevance = _computeRelevance(
contextType: contextType,
);
final returnType = field.type.getDisplayString(
withNullability: _isNonNullableByDefault,
);
_addSuggestion(
CompletionSuggestion(
CompletionSuggestionKind.IDENTIFIER,
relevance,
name,
name.length,
0,
false,
false,
returnType: returnType,
),
);
}
/// Add a suggestion for a static field declared within a class or extension.
/// If the field is synthetic, add the corresponding getter instead.
///

View file

@ -10,6 +10,7 @@ import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_plugin/src/utilities/visitors/local_declaration_visitor.dart';
import 'package:collection/collection.dart';
/// A contributor that produces suggestions based on the instance members of a
/// given type, whether declared by that type directly or inherited from a
@ -89,6 +90,24 @@ class TypeMemberContributor extends DartCompletionContributor {
var memberBuilder = _SuggestionBuilder(request, builder);
memberBuilder.buildSuggestions(type,
mixins: mixins, superclassConstraints: superclassConstraints);
} else if (type is RecordType) {
_suggestFromRecordType(type);
}
}
void _suggestFromRecordType(RecordType type) {
type.positionalFields.forEachIndexed((index, field) {
builder.suggestRecordField(
field: field,
name: '\$$index',
);
});
for (final field in type.namedFields) {
builder.suggestRecordField(
field: field,
name: field.name,
);
}
}
}

View file

@ -269,6 +269,11 @@ extension CompletionSuggestionExtension
element.isNotNull.kind.isParameter;
}
void get isRecordField {
kind.isIdentifier;
element.isNull;
}
void get isSetter {
kind.isIdentifier;
element.isNotNull.kind.isSetter;

View file

@ -0,0 +1,95 @@
// 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:analyzer_utilities/check/check.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../../client/completion_driver_test.dart';
import '../completion_check.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(RecordTypeTest1);
defineReflectiveTests(RecordTypeTest2);
});
}
@reflectiveTest
class RecordTypeTest1 extends AbstractCompletionDriverTest
with RecordTypeTestCases {
@override
TestingCompletionProtocol get protocol => TestingCompletionProtocol.version1;
}
@reflectiveTest
class RecordTypeTest2 extends AbstractCompletionDriverTest
with RecordTypeTestCases {
@override
TestingCompletionProtocol get protocol => TestingCompletionProtocol.version2;
}
mixin RecordTypeTestCases on AbstractCompletionDriverTest {
Future<void> test_mixin() async {
var response = await getTestCodeSuggestions('''
void f((int, {String foo02}) r) {
r.^
}
''');
check(response)
..hasEmptyReplacement()
..suggestions.matchesInAnyOrder([
(suggestion) => suggestion
..completion.isEqualTo(r'$0')
..isRecordField
..returnType.isEqualTo('int'),
(suggestion) => suggestion
..completion.isEqualTo(r'foo02')
..isRecordField
..returnType.isEqualTo('String'),
]);
}
Future<void> test_named() async {
var response = await getTestCodeSuggestions('''
void f(({int foo01, String foo02}) r) {
r.^
}
''');
check(response)
..hasEmptyReplacement()
..suggestions.matchesInAnyOrder([
(suggestion) => suggestion
..completion.isEqualTo(r'foo01')
..isRecordField
..returnType.isEqualTo('int'),
(suggestion) => suggestion
..completion.isEqualTo(r'foo02')
..isRecordField
..returnType.isEqualTo('String'),
]);
}
Future<void> test_positional() async {
var response = await getTestCodeSuggestions('''
void f((int, String) r) {
r.^
}
''');
check(response)
..hasEmptyReplacement()
..suggestions.matchesInAnyOrder([
(suggestion) => suggestion
..completion.isEqualTo(r'$0')
..isRecordField
..returnType.isEqualTo('int'),
(suggestion) => suggestion
..completion.isEqualTo(r'$1')
..isRecordField
..returnType.isEqualTo('String'),
]);
}
}

View file

@ -7,6 +7,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'class_test.dart' as class_;
import 'enum_test.dart' as enum_;
import 'library_test.dart' as library_;
import 'record_type_test.dart' as record_type;
/// Tests suggestions produced for various kinds of declarations.
void main() {
@ -14,5 +15,6 @@ void main() {
class_.main();
enum_.main();
library_.main();
record_type.main();
});
}

View file

@ -29,7 +29,7 @@ class InstanceMemberTest2 extends CompletionRelevanceTest
}
mixin InstanceMemberTestCases on CompletionRelevanceTest {
Future<void> test_contextType() async {
Future<void> test_contextType_interfaceType_method() async {
await addTestFile(r'''
class A {}
class B extends A {}
@ -56,6 +56,48 @@ void g(E e) {
]);
}
Future<void> test_contextType_recordType_named() async {
await addTestFile(r'''
class A {}
class B extends A {}
class C extends B {}
class D {}
void f(B _) {}
void g(({A foo01, B foo02, C foo03, D foo04}) r) {
f(r.^);
}
''');
assertOrder([
suggestionWith(completion: r'foo02'), // same
suggestionWith(completion: r'foo03'), // subtype
suggestionWith(completion: r'foo04'), // unrelated
suggestionWith(completion: r'foo01'), // supertype
]);
}
Future<void> test_contextType_recordType_positional() async {
await addTestFile(r'''
class A {}
class B extends A {}
class C extends B {}
class D {}
void f(B _) {}
void g((A, B, C, D) r) {
f(r.^);
}
''');
assertOrder([
suggestionWith(completion: r'$1'), // same
suggestionWith(completion: r'$2'), // subtype
suggestionWith(completion: r'$3'), // unrelated
suggestionWith(completion: r'$0'), // supertype
]);
}
Future<void> test_elementKind() async {
await addTestFile('''
class A {