Skip to content

Commit

Permalink
Fixes for uninitialized fields of Flutter widgets create constructors…
Browse files Browse the repository at this point in the history
… in Flutter style.

[email protected], [email protected]

Bug: flutter/flutter-intellij#2021
Change-Id: Ia5c8989d463ce7071b31069313e4638dea7b13a6
Reviewed-on: https://dart-review.googlesource.com/48700
Commit-Queue: Konstantin Shcheglov <[email protected]>
Reviewed-by: Brian Wilkerson <[email protected]>
  • Loading branch information
scheglov authored and [email protected] committed Mar 28, 2018
1 parent c9e9c1d commit 859632b
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 16 deletions.
103 changes: 87 additions & 16 deletions pkg/analysis_server/lib/src/services/correction/fix_internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<String> fieldNames = <String>[];
for (ClassMember member in classDeclaration.members) {
Expand All @@ -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<String> 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);
}
Expand Down Expand Up @@ -2965,22 +3014,44 @@ class FixProcessor {
return;
}
ConstructorDeclaration constructor = node.parent;
// add these fields
List<FormalParameter> parameters = constructor.parameters.parameters;

ClassDeclaration classNode = constructor.parent;
InterfaceType superType = classNode.element.supertype;

// Compute uninitialized final fields.
List<FieldElement> 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<FormalParameter> 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
Expand Down
107 changes: 107 additions & 0 deletions pkg/analysis_server/test/services/correction/fix_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Widget> 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<Widget> 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 {
Expand Down

0 comments on commit 859632b

Please sign in to comment.