Add a new fix for representationFieldModifier

This add a fix to add a type annotation in place of the `var` keyword.

The assist to add a type annotation was already being offered, but it
didn't understand that it needed to also remove the keyword. The added
fix will do both, and, by being a fix, will appear higher in the list
of code actions that users see, along side the fix to remove the
keyword without adding a type annotation.

The fix to remove the keyword can be bulk applied, so I couldn't make
the new one also be bulk applicable. We might consider reversing the
status so that the default bulk fix behavior includes adding a type
annotation, but this CL doesn't do that.

Change-Id: I122221ef1e5119a76e1157a9a15cf1193135fc59
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/507161
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Brian Wilkerson
2026-05-29 17:02:11 -07:00
parent ad5133b373
commit 8a5987013f
4 changed files with 61 additions and 16 deletions
@@ -21,14 +21,25 @@ class AddTypeAnnotation extends ResolvedCorrectionProducer {
@override
final CorrectionApplicability applicability;
final bool forRepresentationField;
/// Initializes a newly created instance that can't apply bulk and in-file
/// fixes.
new({required super.context})
: applicability = CorrectionApplicability.singleLocation;
: applicability = CorrectionApplicability.singleLocation,
forRepresentationField = false;
/// Initializes a newly created instance that can apply bulk and in-file fixes.
/// Initializes a newly created instance that can apply bulk and in-file
/// fixes.
new bulkFixable({required super.context})
: applicability = CorrectionApplicability.automatically;
: applicability = CorrectionApplicability.automatically,
forRepresentationField = false;
/// Initializes a newly created instance that will replace the keyword with
/// the added type.
new forRepresentationField({required super.context})
: applicability = CorrectionApplicability.singleLocation,
forRepresentationField = true;
@override
AssistKind get assistKind => DartAssistKind.addTypeAnnotation;
@@ -92,7 +103,8 @@ class AddTypeAnnotation extends ResolvedCorrectionProducer {
) async {
await builder.addDartFileEdit(file, (builder) {
if (builder.canWriteType(type, offset: name.offset)) {
if (keyword != null && keyword.keyword == Keyword.VAR) {
if (keyword != null &&
(forRepresentationField || keyword.keyword == Keyword.VAR)) {
builder.addReplacement(range.token(keyword), (builder) {
builder.writeType(type);
});
@@ -137,7 +149,7 @@ class AddTypeAnnotation extends ResolvedCorrectionProducer {
if (parameter.type != null) {
return;
}
// Ensure that the parameter is named.
// Ensure that the parameter has a named.
var name = parameter.name;
if (name == null) {
return;
@@ -145,15 +157,18 @@ class AddTypeAnnotation extends ResolvedCorrectionProducer {
// Prepare the type.
var type = parameter.declaredFragment!.element.type;
// TODO(scheglov): If the parameter is in a method declaration, and if the
// method overrides a method that has a type for the corresponding
// parameter, it would be nice to copy down the type from the overridden
// method.
// method overrides a method that has a type for the corresponding
// parameter, it would be nice to copy down the type from the overridden
// method.
if (type is! InterfaceType &&
// type is! FunctionType &&
// type is! FunctionType &&
type is! RecordType) {
return;
}
await _applyChange(builder, null, name, type);
var keyword = forRepresentationField
? parameter.constFinalOrVarKeyword
: null;
await _applyChange(builder, keyword, name, type);
}
Future<void> _forVariableDeclaration(
@@ -738,7 +738,10 @@ final _builtInNonLintGenerators = <DiagnosticCode, List<ProducerGenerator>>{
ConvertIntoBlockBody.missingBody,
],
diag.recordLiteralOnePositionalNoTrailingCommaByType: [AddTrailingComma.new],
diag.representationFieldModifier: [RemoveKeyword.varKeyword],
diag.representationFieldModifier: [
AddTypeAnnotation.forRepresentationField,
RemoveKeyword.varKeyword,
],
diag.returnOfInvalidTypeFromClosure: [AddAsync.wrongReturnType],
diag.returnOfInvalidTypeFromFunction: [
AddAsync.wrongReturnType,
@@ -18,6 +18,7 @@ void main() {
defineReflectiveTests(PreferTypingUninitializedVariablesBulkTest);
defineReflectiveTests(PreferTypingUninitializedVariablesInFileTest);
defineReflectiveTests(PreferTypingUninitializedVariablesLintTest);
defineReflectiveTests(RepresentationFieldModifierTest);
defineReflectiveTests(SpecifyNonObviousLocalVariableTypesBulkTest);
defineReflectiveTests(SpecifyNonObviousLocalVariableTypesInFileTest);
defineReflectiveTests(SpecifyNonObviousLocalVariableTypesLintTest);
@@ -453,6 +454,21 @@ void f() {
}
}
@reflectiveTest
class RepresentationFieldModifierTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.addTypeAnnotation;
Future<void> test_var() async {
await resolveTestCode('''
extension type E(var v);
''');
await assertHasFix('''
extension type E(Object? v);
''');
}
}
@reflectiveTest
class SpecifyNonObviousLocalVariableTypesBulkTest extends BulkFixProcessorTest {
@override
@@ -154,8 +154,12 @@ abstract class BulkFixProcessorTest extends AbstractSingleUnitTest {
expect(resultCode, normalizeSource(expectedCode));
}
Future<void> assertHasFix(String expected, {bool isParse = false}) async {
change = await _computeSourceChange(isParse: isParse);
Future<void> assertHasFix(
String expected, {
List<String>? codes,
bool isParse = false,
}) async {
change = await _computeSourceChange(codes: codes, isParse: isParse);
// apply to "file"
var fileEdits = change.edits;
@@ -184,11 +188,15 @@ abstract class BulkFixProcessorTest extends AbstractSingleUnitTest {
}
/// Computes fixes for the specified [testUnit].
Future<BulkFixProcessor> computeFixes({bool isParse = false}) async {
Future<BulkFixProcessor> computeFixes({
List<String>? codes,
bool isParse = false,
}) async {
var analysisContext = contextFor(testFile);
var processor = BulkFixProcessor(
TestInstrumentationService(),
await workspace,
codes: codes,
);
if (isParse) {
await processor.fixErrorsUsingParsedResult([analysisContext]);
@@ -214,8 +222,11 @@ abstract class BulkFixProcessorTest extends AbstractSingleUnitTest {
}
/// Returns the source change for computed fixes in the specified [testUnit].
Future<SourceChange> _computeSourceChange({bool isParse = false}) async {
processor = await computeFixes(isParse: isParse);
Future<SourceChange> _computeSourceChange({
List<String>? codes,
bool isParse = false,
}) async {
processor = await computeFixes(codes: codes, isParse: isParse);
return processor.builder.sourceChange;
}