Add a fix to inline a typedef

I needed to fix AST nodes for parameters to know about the required
token when computing the beginning token and child entities.

Change-Id: Icb0fc27bc1e9f6650376f5870944fb8f592a5e8c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/138744
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Brian Wilkerson
2020-03-09 14:17:07 +00:00
committed by commit-bot@chromium.org
parent 0f284647b1
commit a629dbf073
8 changed files with 343 additions and 12 deletions
@@ -0,0 +1,146 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
class InlineTypedef extends CorrectionProducer {
String name;
@override
List<Object> get fixArguments => [name];
@override
FixKind get fixKind => DartFixKind.INLINE_TYPEDEF;
@override
Future<void> compute(DartChangeBuilder builder) async {
//
// Extract the information needed to build the edit.
//
TypeAnnotation returnType;
TypeParameterList typeParameters;
List<FormalParameter> parameters;
if (node is FunctionTypeAlias) {
var typedef = node as FunctionTypeAlias;
returnType = typedef.returnType;
name = typedef.name.name;
typeParameters = typedef.typeParameters;
parameters = typedef.parameters.parameters;
} else if (node is GenericTypeAlias) {
var typedef = node as GenericTypeAlias;
if (typedef.typeParameters != null) {
return;
}
var functionType = typedef.functionType;
returnType = functionType.returnType;
name = typedef.name.name;
typeParameters = functionType.typeParameters;
parameters = functionType.parameters.parameters;
} else {
return;
}
// TODO(brianwilkerson) Handle parts.
var finder = _ReferenceFinder(name);
resolvedResult.unit.accept(finder);
if (finder.count != 1) {
return;
}
//
// Build the edit.
//
await builder.addFileEdit(file, (DartFileEditBuilder builder) {
builder.addDeletion(utils.getLinesRange(range.node(node)));
builder.addReplacement(range.node(finder.reference),
(DartEditBuilder builder) {
if (returnType != null) {
builder.write(utils.getNodeText(returnType));
builder.write(' ');
}
builder.write('Function');
if (typeParameters != null) {
builder.write(utils.getNodeText(typeParameters));
}
String groupEnd;
builder.write('(');
for (int i = 0; i < parameters.length; i++) {
var parameter = parameters[i];
if (i > 0) {
// This intentionally drops any trailing comma in order to improve
// formatting.
builder.write(', ');
}
if (parameter is DefaultFormalParameter) {
if (groupEnd == null) {
if (parameter.isNamed) {
groupEnd = '}';
builder.write('{');
} else {
groupEnd = ']';
builder.write('[');
}
}
parameter = (parameter as DefaultFormalParameter).parameter;
}
if (parameter is FunctionTypedFormalParameter) {
builder.write(utils.getNodeText(parameter));
} else if (parameter is SimpleFormalParameter) {
if (parameter.metadata.isNotEmpty) {
builder
.write(utils.getRangeText(range.nodes(parameter.metadata)));
}
if (parameter.requiredKeyword != null) {
builder.write('required ');
}
if (parameter.covariantKeyword != null) {
builder.write('covariant ');
}
var keyword = parameter.keyword;
if (keyword != null && keyword.type != Keyword.VAR) {
builder.write(keyword.lexeme);
}
if (parameter.type == null) {
builder.write('dynamic');
} else {
builder.write(utils.getNodeText(parameter.type));
}
if (parameter.isNamed) {
builder.write(' ');
builder.write(parameter.identifier.name);
}
}
}
if (groupEnd != null) {
builder.write(groupEnd);
}
builder.write(')');
});
});
}
}
class _ReferenceFinder extends RecursiveAstVisitor {
final String typeName;
TypeName reference;
int count = 0;
_ReferenceFinder(this.typeName);
@override
void visitTypeName(TypeName node) {
if (node.name.name == typeName) {
reference ??= node;
count++;
}
super.visitTypeName(node);
}
}
@@ -268,6 +268,8 @@ class DartFixKind {
FixKind('IMPORT_LIBRARY_SHOW', 55, "Update library '{0}' import");
static const INLINE_INVOCATION =
FixKind('INLINE_INVOCATION', 30, "Inline invocation of '{0}'");
static const INLINE_TYPEDEF =
FixKind('INLINE_TYPEDEF', 30, "Inline the definition of '{0}'");
static const INSERT_SEMICOLON = FixKind('INSERT_SEMICOLON', 50, "Insert ';'");
static const MAKE_CLASS_ABSTRACT =
FixKind('MAKE_CLASS_ABSTRACT', 50, "Make class '{0}' abstract");
@@ -18,6 +18,7 @@ import 'package:analysis_server/src/services/correction/dart/convert_to_map_lite
import 'package:analysis_server/src/services/correction/dart/convert_to_null_aware.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_set_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_where_type.dart';
import 'package:analysis_server/src/services/correction/dart/inline_typedef.dart';
import 'package:analysis_server/src/services/correction/dart/remove_dead_if_null.dart';
import 'package:analysis_server/src/services/correction/dart/remove_if_null_operator.dart';
import 'package:analysis_server/src/services/correction/dart/replace_with_eight_digit_hex.dart';
@@ -4717,7 +4718,9 @@ class FixProcessor extends BaseProcessor {
await compute(RemoveDeadIfNull());
} else if (errorCode is LintCode) {
String name = errorCode.name;
if (name == LintNames.avoid_returning_null_for_future) {
if (name == LintNames.avoid_private_typedef_functions) {
await compute(InlineTypedef());
} else if (name == LintNames.avoid_returning_null_for_future) {
await compute(WrapInFuture());
} else if (name == LintNames.prefer_collection_literals) {
await compute(ConvertToListLiteral());
@@ -19,6 +19,8 @@ class LintNames {
static const String avoid_relative_lib_imports = 'avoid_relative_lib_imports';
static const String avoid_return_types_on_setters =
'avoid_return_types_on_setters';
static const String avoid_private_typedef_functions =
'avoid_private_typedef_functions';
static const String avoid_returning_null_for_future =
'avoid_returning_null_for_future';
static const String avoid_types_on_closure_parameters =
@@ -27,13 +27,17 @@ abstract class FixProcessorLintTest extends FixProcessorTest {
/// The offset of the lint marker in the code being analyzed.
int lintOffset = -1;
/// Return a list of the experiments that are to be enabled for tests in this
/// class, or `null` if there are no experiments that should be enabled.
List<String> get experiments => null;
/// Return the lint code being tested.
String get lintCode;
@override
void setUp() {
super.setUp();
createAnalysisOptionsFile(lints: [lintCode]);
createAnalysisOptionsFile(experiments: experiments, lints: [lintCode]);
}
/// Find the error that is to be fixed by computing the errors in the file,
@@ -0,0 +1,162 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'fix_processor.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(InlineTypedefTest);
defineReflectiveTests(InlineTypedefWithNNBDTest);
});
}
@reflectiveTest
class InlineTypedefTest extends FixProcessorLintTest {
@override
FixKind get kind => DartFixKind.INLINE_TYPEDEF;
@override
String get lintCode => LintNames.avoid_private_typedef_functions;
Future<void> test_generic_parameter_optionalNamed() async {
await resolveTestUnit('''
typedef _F = Function({int i});
void g(_F f) {}
''');
await assertHasFix('''
void g(Function({int i}) f) {}
''');
}
Future<void> test_generic_parameter_optionalPositional_withName() async {
await resolveTestUnit('''
typedef _F = Function([int i]);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function([int]) f) {}
''');
}
Future<void> test_generic_parameter_optionalPositional_withoutName() async {
await resolveTestUnit('''
typedef _F = Function([int]);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function([int]) f) {}
''');
}
Future<void> test_generic_parameter_requiredPositional_withName() async {
await resolveTestUnit('''
typedef _F = Function(int i);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function(int) f) {}
''');
}
Future<void> test_generic_parameter_requiredPositional_withoutName() async {
await resolveTestUnit('''
typedef _F = Function(int);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function(int) f) {}
''');
}
Future<void> test_generic_returnType() async {
await resolveTestUnit('''
typedef _F = void Function();
void g(_F f) {}
''');
await assertHasFix('''
void g(void Function() f) {}
''');
}
Future<void> test_generic_typeParameters() async {
await resolveTestUnit('''
typedef _F = Function<T>(T);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function<T>(T) f) {}
''');
}
Future<void> test_nonGeneric_parameter_requiredPositional_typed() async {
await resolveTestUnit('''
typedef _F(int i);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function(int) f) {}
''');
}
Future<void> test_nonGeneric_parameter_requiredPositional_untyped() async {
await resolveTestUnit('''
typedef _F(i);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function(dynamic) f) {}
''');
}
Future<void> test_nonGeneric_returnType() async {
await resolveTestUnit('''
typedef void _F();
void g(_F f) {}
''');
await assertHasFix('''
void g(void Function() f) {}
''');
}
Future<void> test_nonGeneric_typeParameters() async {
await resolveTestUnit('''
typedef _F<T>(T t);
void g(_F f) {}
''');
await assertHasFix('''
void g(Function<T>(T) f) {}
''');
}
}
@reflectiveTest
class InlineTypedefWithNNBDTest extends InlineTypedefTest {
@override
List<String> get experiments => ['non-nullable'];
Future<void> test_generic_parameter_requiredNamed() async {
await resolveTestUnit('''
typedef _F = Function({required int i});
void g(_F f) {}
''');
await assertHasFix('''
void g(Function({required int i}) f) {}
''');
}
Future<void> test_nonGeneric_parameter_requiredNamed() async {
await resolveTestUnit('''
typedef _F({required int i});
void g(_F f) {}
''');
await assertHasFix('''
void g(Function({required int i}) f) {}
''');
}
}
@@ -82,6 +82,7 @@ import 'import_library_project_test.dart' as import_library_project;
import 'import_library_sdk_test.dart' as import_library_sdk;
import 'import_library_show_test.dart' as import_library_show;
import 'inline_invocation_test.dart' as inline_invocation;
import 'inline_typedef_test.dart' as inline_typedef;
import 'insert_semicolon_test.dart' as insert_semicolon;
import 'make_class_abstract_test.dart' as make_class_abstract;
import 'make_field_not_final_test.dart' as make_field_not_final;
@@ -221,6 +222,7 @@ void main() {
import_library_sdk.main();
import_library_show.main();
inline_invocation.main();
inline_typedef.main();
insert_semicolon.main();
make_class_abstract.main();
make_field_not_final.main();
+20 -10
View File
@@ -1393,14 +1393,14 @@ class ChildEntities
@override
Iterator<SyntacticEntity> get iterator => _entities.iterator;
/// Add an AST node or token as the next child entity, if it is not null.
/// Add an AST node or token as the next child entity, if it is not `null`.
void add(SyntacticEntity entity) {
if (entity != null) {
_entities.add(entity);
}
}
/// Add the given items as the next child entities, if [items] is not null.
/// Add the given items as the next child entities, if [items] is not `null`.
void addAll(Iterable<SyntacticEntity> items) {
if (items != null) {
_entities.addAll(items);
@@ -4067,6 +4067,8 @@ class FieldFormalParameterImpl extends NormalFormalParameterImpl
NodeList<Annotation> metadata = this.metadata;
if (metadata.isNotEmpty) {
return metadata.beginToken;
} else if (requiredKeyword != null) {
return requiredKeyword;
} else if (covariantKeyword != null) {
return covariantKeyword;
} else if (keyword != null) {
@@ -5166,11 +5168,19 @@ class FunctionTypedFormalParameterImpl extends NormalFormalParameterImpl
}
@override
Token get beginToken =>
this.metadata.beginToken ??
covariantKeyword ??
_returnType?.beginToken ??
identifier?.beginToken;
Token get beginToken {
NodeList<Annotation> metadata = this.metadata;
if (metadata.isNotEmpty) {
return metadata.beginToken;
} else if (requiredKeyword != null) {
return requiredKeyword;
} else if (covariantKeyword != null) {
return covariantKeyword;
} else if (_returnType != null) {
return _returnType.beginToken;
}
return identifier?.beginToken;
}
@override
Iterable<SyntacticEntity> get childEntities =>
@@ -7691,9 +7701,7 @@ abstract class NormalFormalParameterImpl extends FormalParameterImpl
} else {
result.addAll(sortedCommentAndAnnotations);
}
if (covariantKeyword != null) {
result.add(covariantKeyword);
}
result..add(requiredKeyword)..add(covariantKeyword);
return result;
}
@@ -8725,6 +8733,8 @@ class SimpleFormalParameterImpl extends NormalFormalParameterImpl
NodeList<Annotation> metadata = this.metadata;
if (metadata.isNotEmpty) {
return metadata.beginToken;
} else if (requiredKeyword != null) {
return requiredKeyword;
} else if (covariantKeyword != null) {
return covariantKeyword;
} else if (keyword != null) {