From 8a5987013fbd3a7e66604f6eedbcbc197bda32da Mon Sep 17 00:00:00 2001 From: Brian Wilkerson Date: Fri, 29 May 2026 17:02:11 -0700 Subject: [PATCH] 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 --- .../correction/dart/add_type_annotation.dart | 35 +++++++++++++------ .../src/services/correction/fix_internal.dart | 5 ++- .../fix/add_type_annotation_test.dart | 16 +++++++++ .../correction/fix/fix_processor.dart | 21 ++++++++--- 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart b/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart index 4faf04dcf39..d2c68fc57de 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart @@ -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 _forVariableDeclaration( diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart index f8fc5512ea5..51f757a145b 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart @@ -738,7 +738,10 @@ final _builtInNonLintGenerators = >{ ConvertIntoBlockBody.missingBody, ], diag.recordLiteralOnePositionalNoTrailingCommaByType: [AddTrailingComma.new], - diag.representationFieldModifier: [RemoveKeyword.varKeyword], + diag.representationFieldModifier: [ + AddTypeAnnotation.forRepresentationField, + RemoveKeyword.varKeyword, + ], diag.returnOfInvalidTypeFromClosure: [AddAsync.wrongReturnType], diag.returnOfInvalidTypeFromFunction: [ AddAsync.wrongReturnType, diff --git a/pkg/analysis_server/test/src/services/correction/fix/add_type_annotation_test.dart b/pkg/analysis_server/test/src/services/correction/fix/add_type_annotation_test.dart index da7b21750aa..ca78f66d1eb 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/add_type_annotation_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/add_type_annotation_test.dart @@ -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 test_var() async { + await resolveTestCode(''' +extension type E(var v); +'''); + await assertHasFix(''' +extension type E(Object? v); +'''); + } +} + @reflectiveTest class SpecifyNonObviousLocalVariableTypesBulkTest extends BulkFixProcessorTest { @override diff --git a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart index 4fd0a73461a..26935808ebe 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart @@ -154,8 +154,12 @@ abstract class BulkFixProcessorTest extends AbstractSingleUnitTest { expect(resultCode, normalizeSource(expectedCode)); } - Future assertHasFix(String expected, {bool isParse = false}) async { - change = await _computeSourceChange(isParse: isParse); + Future assertHasFix( + String expected, { + List? 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 computeFixes({bool isParse = false}) async { + Future computeFixes({ + List? 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 _computeSourceChange({bool isParse = false}) async { - processor = await computeFixes(isParse: isParse); + Future _computeSourceChange({ + List? codes, + bool isParse = false, + }) async { + processor = await computeFixes(codes: codes, isParse: isParse); return processor.builder.sourceChange; }