Fix bug in AddDiagnosticReference when using it via CLI.

Fixes https://github.com/dart-lang/sdk/issues/55772.

Change-Id: Ic77f67486f71bdfea726ea3161d83728326b2c5b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372120
Commit-Queue: Keerti Parthasarathy <keertip@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Keerti Parthasarathy
2024-06-20 18:10:32 +00:00
committed by Commit Queue
parent acd82f974b
commit bcbe3a54ff
2 changed files with 319 additions and 4 deletions
@@ -4,11 +4,14 @@
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analysis_server/src/utilities/extensions/flutter.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
@@ -54,6 +57,11 @@ class AddDiagnosticPropertyReference extends ResolvedCorrectionProducer {
return;
}
if (applyingBulkFixes) {
await _fixAllDiagnosticPropertyReferences(builder, classDeclaration);
return;
}
var type = _getReturnType(node);
if (type == null) {
return;
@@ -191,6 +199,222 @@ class AddDiagnosticPropertyReference extends ResolvedCorrectionProducer {
}
}
/// Fixes all instances of the [LintNames.diagnostic_describe_all_properties] in the given
/// [declaration].
Future<void> _fixAllDiagnosticPropertyReferences(
ChangeBuilder builder, ClassDeclaration declaration) async {
var propertyErrors = _getAllDiagnosticsInClass(declaration);
// Create fixes only when its the first error.
if (propertyErrors.isNotEmpty &&
errorOffset != propertyErrors.first.offset) {
return;
}
void writePropertyReference(DartEditBuilder builder,
{required String prefix,
required String builderName,
required _PropertyInfo property}) {
builder.write('$prefix$builderName.add(${property.constructorId}');
var type = property.type;
if (property.typeArgs != null) {
builder.write('<');
builder.writeTypes(property.typeArgs);
builder.write('>');
} else if (type is DynamicType || type is InvalidType) {
var declType = property.declType;
if (declType != null) {
var typeText = utils.getNodeText(declType);
if (typeText != 'dynamic') {
builder.write('<');
builder.write(utils.getNodeText(declType));
builder.write('>');
}
}
}
builder.writeln(
"${property.constructorName}('${property.name}', ${property.name}));");
}
var properties = <_PropertyInfo>[];
// Compute the information for all the properties to be added.
for (var error in propertyErrors) {
var selectionOffset = error.offset;
var selectionEnd = selectionOffset + error.length;
var locator = NodeLocator(selectionOffset, selectionEnd);
var node = locator.searchWithin(unitResult.unit);
if (node == null) {
continue;
}
var propertyInfo = _getPropertyInfo(node);
if (propertyInfo.type != null) {
properties.add(propertyInfo);
}
}
if (properties.isEmpty) {
return;
}
var debugFillProperties = declaration.members
.whereType<MethodDeclaration>()
.where((e) => e.name.lexeme == 'debugFillProperties')
.singleOrNull;
if (debugFillProperties == null) {
await builder.addDartFileEdit(file, (builder) {
builder.insertMethod(declaration, (builder) {
var declPrefix = utils.oneIndent;
var bodyPrefix = utils.twoIndents;
builder.writeln('@override');
builder.writeln(
'${declPrefix}void debugFillProperties(DiagnosticPropertiesBuilder properties) {');
builder
.writeln('${bodyPrefix}super.debugFillProperties(properties);');
for (var property in properties) {
writePropertyReference(builder,
prefix: bodyPrefix,
builderName: 'properties',
property: property);
}
builder.write('$declPrefix}');
});
});
return;
}
var body = debugFillProperties.body;
if (body is BlockFunctionBody) {
var functionBody = body;
int offset;
String prefix;
if (functionBody.block.statements.isEmpty) {
offset = functionBody.block.leftBracket.offset;
prefix = utils.getLinePrefix(offset) + utils.oneIndent;
} else {
offset = functionBody.block.statements.last.endToken.offset;
prefix = utils.getLinePrefix(offset);
}
var parameterList = debugFillProperties.parameters;
if (parameterList == null) {
return;
}
String? propertiesBuilderName;
for (var parameter in parameterList.parameters) {
if (parameter is SimpleFormalParameter) {
var type = parameter.type;
var identifier = parameter.name;
if (type is NamedType && identifier != null) {
if (type.name2.lexeme == 'DiagnosticPropertiesBuilder') {
propertiesBuilderName = identifier.lexeme;
break;
}
}
}
}
if (propertiesBuilderName == null) {
return;
}
var final_propertiesBuilderName = propertiesBuilderName;
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(utils.getLineNext(offset), (builder) {
for (var property in properties) {
writePropertyReference(builder,
prefix: prefix,
builderName: final_propertiesBuilderName,
property: property);
}
});
});
}
}
/// Returns a list of all the [AnalysisError]'s of type
/// [LintNames.diagnostic_describe_all_properties] fpr the given [declaration].
List<AnalysisError> _getAllDiagnosticsInClass(ClassDeclaration declaration) {
var propertyErrors = <AnalysisError>[];
var startOffset = declaration.offset;
var endOffset = startOffset + declaration.length;
for (var error in unitResult.errors) {
var errorCode = error.errorCode;
if (errorCode.type == ErrorType.LINT &&
errorCode.name == LintNames.diagnostic_describe_all_properties &&
error.offset > startOffset &&
error.offset < endOffset) {
propertyErrors.add(error);
}
}
return propertyErrors;
}
/// Computes the information for the proerty at the given [node].
_PropertyInfo _getPropertyInfo(AstNode node) {
String? name;
if (node is MethodDeclaration) {
name = node.name.lexeme;
} else if (node is VariableDeclaration) {
name = node.name.lexeme;
}
var type = _getReturnType(node);
if (type == null) {
return _PropertyInfo(name, type, '', [], '', null);
}
String constructorId;
List<DartType>? typeArgs;
var constructorName = '';
if (type is FunctionType) {
constructorId = 'ObjectFlagProperty';
typeArgs = [type];
constructorName = '.has';
} else if (type.isDartCoreInt) {
constructorId = 'IntProperty';
} else if (type.isDartCoreDouble) {
constructorId = 'DoubleProperty';
} else if (type.isDartCoreString) {
constructorId = 'StringProperty';
} else if (_isEnum(type)) {
constructorId = 'EnumProperty';
typeArgs = [type];
} else if (_isIterable(type)) {
constructorId = 'IterableProperty';
typeArgs = (type as InterfaceType).typeArguments;
} else if (type.isColor) {
constructorId = 'ColorProperty';
} else if (type.isMatrix4) {
constructorId = 'TransformProperty';
} else {
constructorId = 'DiagnosticsProperty';
if (!(type is DynamicType || type is InvalidType)) {
typeArgs = [type];
}
}
TypeAnnotation? declType;
if (type is DynamicType || type is InvalidType) {
var decl = node.thisOrAncestorOfType<VariableDeclarationList>();
if (decl != null) {
declType = decl.type;
// getter
} else if (node is MethodDeclaration) {
declType = node.returnType;
}
}
return _PropertyInfo(
name, type, constructorId, typeArgs, constructorName, declType);
}
/// Return the return type of the given [node].
DartType? _getReturnType(AstNode node) {
if (node is MethodDeclaration) {
@@ -217,3 +441,15 @@ class AddDiagnosticPropertyReference extends ResolvedCorrectionProducer {
return type.asInstanceOf(typeProvider.iterableElement) != null;
}
}
class _PropertyInfo {
final String? name;
final DartType? type;
final String constructorId;
final List<DartType>? typeArgs;
final String constructorName;
final TypeAnnotation? declType;
_PropertyInfo(this.name, this.type, this.constructorId, this.typeArgs,
this.constructorName, this.declType);
}
@@ -22,6 +22,88 @@ class AddDiagnosticPropertyReferenceBulkTest extends BulkFixProcessorTest {
@override
String get lintCode => LintNames.diagnostic_describe_all_properties;
@override
void setUp() {
super.setUp();
writeTestPackageConfig(
flutter: true,
);
}
Future<void> test_multiple_no_debugFillPropertiesMethod() async {
createAnalysisOptionsFile(
lints: [LintNames.diagnostic_describe_all_properties]);
await resolveTestCode(r'''
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class C extends Widget with Diagnosticable {
bool get absorbing => _absorbing;
bool _absorbing = false;
String logBuffer = '';
}
''');
await assertHasFix(r'''
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class C extends Widget with Diagnosticable {
bool get absorbing => _absorbing;
bool _absorbing = false;
String logBuffer = '';
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
properties.add(StringProperty('logBuffer', logBuffer));
}
}
''');
}
Future<void> test_multiple_with_debugFillPropertiesMethod() async {
createAnalysisOptionsFile(
lints: [LintNames.diagnostic_describe_all_properties]);
await resolveTestCode(r'''
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class C extends Widget with Diagnosticable {
bool get absorbing => _absorbing;
bool _absorbing = false;
String logBuffer = '';
int field = 0;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
}
}
''');
await assertHasFix(r'''
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class C extends Widget with Diagnosticable {
bool get absorbing => _absorbing;
bool _absorbing = false;
String logBuffer = '';
int field = 0;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
properties.add(StringProperty('logBuffer', logBuffer));
properties.add(IntProperty('field', field));
}
}
''');
}
Future<void> test_singleFile() async {
writeTestPackageConfig(flutter: true);
await resolveTestCode('''
@@ -388,10 +470,6 @@ import 'package:flutter/widgets.dart';
class C extends Widget with Diagnosticable {
Iterable<String> field = [];
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
}
}
''');
await assertHasFix('''
@@ -400,6 +478,7 @@ import 'package:flutter/widgets.dart';
class C extends Widget with Diagnosticable {
Iterable<String> field = [];
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);