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 bb4a3b31c7a24..40f5f530ade49 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart @@ -27,6 +27,7 @@ import 'package:analyzer/error/error.dart'; import 'package:analyzer/exception/exception.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/src/dart/analysis/driver.dart'; +import 'package:analyzer/src/dart/analysis/session_helper.dart'; import 'package:analyzer/src/dart/analysis/top_level_declaration.dart'; import 'package:analyzer/src/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/utilities.dart'; @@ -107,6 +108,16 @@ class FixProcessor { */ AnalysisDriver driver; + /** + * The analysis session to be used to create the change builder. + */ + AnalysisSession session; + + /** + * The helper wrapper around the [session]. + */ + AnalysisSessionHelper sessionHelper; + String file; CompilationUnitElement unitElement; Source unitSource; @@ -131,7 +142,11 @@ class FixProcessor { resourceProvider = dartContext.resourceProvider; astProvider = dartContext.astProvider; getTopLevelDeclarations = dartContext.getTopLevelDeclarations; + driver = dartContext.analysisDriver; + session = driver.currentSession; + sessionHelper = new AnalysisSessionHelper(session); + // unit unit = dartContext.unit; unitElement = unit.element; @@ -154,11 +169,6 @@ class FixProcessor { */ String get eol => utils.endOfLine; - /** - * Return the analysis session to be used to create the change builder. - */ - AnalysisSession get session => driver.currentSession; - TypeProvider get typeProvider { if (_typeProvider == null) { _typeProvider = unitElement.context.typeProvider; @@ -1110,11 +1120,15 @@ class FixProcessor { if (node is! SimpleIdentifier || node.parent is! VariableDeclaration) { return; } + ClassDeclaration classDeclaration = node.getAncestor((node) => node is ClassDeclaration); if (classDeclaration == null) { return; } + String className = classDeclaration.name.name; + InterfaceType superType = classDeclaration.element.supertype; + // prepare names of uninitialized final fields List fieldNames = []; for (ClassMember member in classDeclaration.members) { @@ -1130,15 +1144,50 @@ class FixProcessor { // prepare location for a new constructor ClassMemberLocation targetLocation = utils.prepareNewConstructorLocation(classDeclaration); + DartChangeBuilder changeBuilder = new DartChangeBuilder(session); - await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { - builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { - builder.write(targetLocation.prefix); - builder.writeConstructorDeclaration(classDeclaration.name.name, - fieldNames: fieldNames); - builder.write(targetLocation.suffix); + if (flutter.isExactlyStatelessWidgetType(superType) || + flutter.isExactlyStatefulWidgetType(superType)) { + // Specialize for Flutter widgets. + ClassElement keyClass = + await sessionHelper.getClass(flutter.WIDGETS_LIBRARY_URI, 'Key'); + await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { + builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { + builder.write(targetLocation.prefix); + builder.write('const '); + builder.write(className); + builder.write('({'); + builder.writeType(keyClass.type); + builder.write(' key'); + + List childrenFields = []; + for (String fieldName in fieldNames) { + if (fieldName == 'child' || fieldName == 'children') { + childrenFields.add(fieldName); + continue; + } + builder.write(', this.'); + builder.write(fieldName); + } + for (String fieldName in childrenFields) { + builder.write(', this.'); + builder.write(fieldName); + } + + builder.write('}) : super(key: key);'); + builder.write(targetLocation.suffix); + }); }); - }); + } else { + await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { + builder.addInsertion(targetLocation.offset, (DartEditBuilder builder) { + builder.write(targetLocation.prefix); + builder.writeConstructorDeclaration(className, + fieldNames: fieldNames); + builder.write(targetLocation.suffix); + }); + }); + } _addFixFromBuilder( changeBuilder, DartFixKind.CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS); } @@ -2965,22 +3014,44 @@ class FixProcessor { return; } ConstructorDeclaration constructor = node.parent; - // add these fields + List parameters = constructor.parameters.parameters; + + ClassDeclaration classNode = constructor.parent; + InterfaceType superType = classNode.element.supertype; + + // Compute uninitialized final fields. List fields = ErrorVerifier.computeNotInitializedFields(constructor); fields.retainWhere((FieldElement field) => field.isFinal); - // prepare new parameters code + + // Prepare new parameters code. fields.sort((a, b) => a.nameOffset - b.nameOffset); String fieldParametersCode = fields.map((field) => 'this.${field.name}').join(', '); - // prepare the last required parameter + + // Specialize for Flutter widgets. + if (flutter.isExactlyStatelessWidgetType(superType) || + flutter.isExactlyStatefulWidgetType(superType)) { + if (parameters.isNotEmpty && parameters.last.isNamed) { + DartChangeBuilder changeBuilder = new DartChangeBuilder(session); + await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { + builder.addSimpleInsertion( + parameters.last.end, ', $fieldParametersCode'); + }); + _addFixFromBuilder( + changeBuilder, DartFixKind.ADD_FIELD_FORMAL_PARAMETERS); + return; + } + } + + // Prepare the last required parameter. FormalParameter lastRequiredParameter; - List parameters = constructor.parameters.parameters; for (FormalParameter parameter in parameters) { if (parameter.isRequired) { lastRequiredParameter = parameter; } } + DartChangeBuilder changeBuilder = new DartChangeBuilder(session); await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) { // append new field formal initializers diff --git a/pkg/analysis_server/test/services/correction/fix_test.dart b/pkg/analysis_server/test/services/correction/fix_test.dart index 0f232573925c2..457d6a511dae0 100644 --- a/pkg/analysis_server/test/services/correction/fix_test.dart +++ b/pkg/analysis_server/test/services/correction/fix_test.dart @@ -596,6 +596,32 @@ class B {} '''); } + test_addFieldFormalParameters_flutter() async { + addFlutterPackage(); + await resolveTestUnit(''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final int b; + final int c; + + MyWidget({Key key, this.a}) : super(key: key); +} +'''); + await assertHasFix(DartFixKind.ADD_FIELD_FORMAL_PARAMETERS, ''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final int b; + final int c; + + MyWidget({Key key, this.a, this.b, this.c}) : super(key: key); +} +'''); + } + test_addFieldFormalParameters_hasRequiredParameter() async { await resolveTestUnit(''' class Test { @@ -1623,6 +1649,87 @@ class Test { '''); } + test_createConstructor_forFinalFields_flutter() async { + addFlutterPackage(); + errorFilter = (AnalysisError error) { + return error.message.contains("'a'"); + }; + await resolveTestUnit(''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final int b = 2; + final int c; +} +'''); + await assertHasFix(DartFixKind.CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS, ''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final int b = 2; + final int c; + + const MyWidget({Key key, this.a, this.c}) : super(key: key); +} +'''); + } + + test_createConstructor_forFinalFields_flutter_childLast() async { + addFlutterPackage(); + errorFilter = (AnalysisError error) { + return error.message.contains("'a'"); + }; + await resolveTestUnit(''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final Widget child; + final int b; +} +'''); + await assertHasFix(DartFixKind.CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS, ''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final Widget child; + final int b; + + const MyWidget({Key key, this.a, this.b, this.child}) : super(key: key); +} +'''); + } + + test_createConstructor_forFinalFields_flutter_childrenLast() async { + addFlutterPackage(); + errorFilter = (AnalysisError error) { + return error.message.contains("'a'"); + }; + await resolveTestUnit(''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final List children; + final int b; +} +'''); + await assertHasFix(DartFixKind.CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS, ''' +import 'package:flutter/widgets.dart'; + +class MyWidget extends StatelessWidget { + final int a; + final List children; + final int b; + + const MyWidget({Key key, this.a, this.b, this.children}) : super(key: key); +} +'''); + } + test_createConstructor_insteadOfSyntheticDefault() async { await resolveTestUnit(''' class A {