Skip to content

Commit

Permalink
Add fix for creating a parameter
Browse files Browse the repository at this point in the history
The variant of the parameter, optional/required and named/positional, is inferred from the other parameters used in the method.

Change-Id: I0c26e5d16e759978410ba854c0fed0a75d33724f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365826
Reviewed-by: Brian Wilkerson <[email protected]>
Commit-Queue: Moritz Sümmermann <[email protected]>
  • Loading branch information
mosuem authored and Commit Queue committed May 23, 2024
1 parent a17d709 commit 798cbcf
Show file tree
Hide file tree
Showing 5 changed files with 375 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) 2024, 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/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';

class CreateParameter extends ResolvedCorrectionProducer {
String _parameterName = '';

@override
CorrectionApplicability get applicability =>
CorrectionApplicability.singleLocation;

@override
List<String> get fixArguments => [_parameterName];

@override
FixKind get fixKind => DartFixKind.CREATE_PARAMETER;

@override
Future<void> compute(ChangeBuilder builder) async {
var nameNode = node;
if (nameNode is! SimpleIdentifier) {
return;
}
_parameterName = nameNode.name;
// prepare target Statement
var parameters =
node.thisOrAncestorOfType<FunctionExpression>()?.parameters ??
node.thisOrAncestorOfType<MethodDeclaration>()?.parameters ??
node.thisOrAncestorOfType<ConstructorDeclaration>()?.parameters;
if (parameters == null) {
return;
}

var requiredPositionals =
parameters.parameters.where((p) => p.isRequiredPositional);
var namedParameters = parameters.parameters.where((p) => p.isNamed);
var somethingAfterPositionals = requiredPositionals.isNotEmpty &&
parameters.parameters.any((p) => !p.isRequiredPositional);
var somethingBeforeNamed = requiredPositionals.isEmpty &&
parameters.parameters.any((p) => !p.isNamed);
var hasFollowingParameters =
somethingAfterPositionals || somethingBeforeNamed;

// compute type
var type =
inferUndefinedExpressionType(nameNode) ?? typeProvider.dynamicType;
var lastRequiredPositional = requiredPositionals.lastOrNull;
var lastNamed = namedParameters.lastOrNull;
var hasPreviousParameters =
lastRequiredPositional != null || lastNamed != null;
var last = lastRequiredPositional ?? lastNamed;
var trailingComma =
parameters.parameters.lastOrNull?.endToken.next?.lexeme == ',';

int insertionToken;
if (hasPreviousParameters) {
if (trailingComma) {
// After comma
insertionToken = last!.endToken.next!.end;
} else if (hasFollowingParameters) {
// After whitespace after comma
insertionToken = last!.endToken.next!.end + 1;
} else {
// After last, as there is no comma
insertionToken = last!.end;
}
} else {
// At first position
insertionToken = parameters.leftParenthesis.end;
}

await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(insertionToken, (builder) {
//prefix
if (hasPreviousParameters) {
if (trailingComma) {
builder.writeln();
var whitespace = utils.getNodePrefix(last!);
builder.write(whitespace);
} else if (!hasFollowingParameters) {
builder.write(', ');
}
}
builder.writeParameter(
_parameterName,
type: type,
nameGroupName: 'NAME',
typeGroupName: 'TYPE',
isRequiredType: true,
isRequiredNamed: last != null &&
last == lastNamed &&
type.nullabilitySuffix != NullabilitySuffix.question,
);
//suffix
if (trailingComma) {
builder.write(',');
} else if (hasFollowingParameters) {
builder.write(', ');
}
});
builder.addLinkedPosition(range.node(node), 'NAME');
});
}
}
5 changes: 5 additions & 0 deletions pkg/analysis_server/lib/src/services/correction/fix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,11 @@ class DartFixKind {
49,
"Create 'noSuchMethod' method",
);
static const CREATE_PARAMETER = FixKind(
'dart.fix.create.parameter',
DartFixKindPriority.DEFAULT,
"Create required positional parameter '{0}'",
);
static const CREATE_SETTER = FixKind(
'dart.fix.create.setter',
DartFixKindPriority.DEFAULT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import 'package:analysis_server/src/services/correction/dart/create_method_or_fu
import 'package:analysis_server/src/services/correction/dart/create_missing_overrides.dart';
import 'package:analysis_server/src/services/correction/dart/create_mixin.dart';
import 'package:analysis_server/src/services/correction/dart/create_no_such_method.dart';
import 'package:analysis_server/src/services/correction/dart/create_parameter.dart';
import 'package:analysis_server/src/services/correction/dart/create_setter.dart';
import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
import 'package:analysis_server/src/services/correction/dart/extend_class_for_mixin.dart';
Expand Down Expand Up @@ -1259,6 +1260,7 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
CreateField.new,
CreateGetter.new,
CreateLocalVariable.new,
CreateParameter.new,
CreateMethodOrFunction.new,
CreateMixin.new,
CreateSetter.new,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// Copyright (c) 2024, 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:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'fix_processor.dart';

void main() {
defineReflectiveSuite(() {
defineReflectiveTests(CreateParameterTest);
});
}

@reflectiveTest
class CreateParameterTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.CREATE_PARAMETER;

Future<void> test_dynamic_type() async {
await resolveTestCode('''
int f(
int b,
) {
var i = b2;
return i;
}
''');
await assertHasFix('''
int f(
int b,
dynamic b2,
) {
var i = b2;
return i;
}
''');
}

Future<void> test_final_comma() async {
await resolveTestCode('''
int f(
int b,
) {
int i = b2;
return i;
}
''');
await assertHasFix('''
int f(
int b,
int b2,
) {
int i = b2;
return i;
}
''');
}

Future<void> test_method_type() async {
await resolveTestCode('''
class A{
int f(
int b,
) {
int i = b2;
return i;
}
}
''');
await assertHasFix('''
class A{
int f(
int b,
int b2,
) {
int i = b2;
return i;
}
}
''');
}

Future<void> test_multi() async {
await resolveTestCode('''
int f(int b) {
int i = b2;
return i;
}
''');
await assertHasFix('''
int f(int b, int b2) {
int i = b2;
return i;
}
''');
}

Future<void> test_only() async {
await resolveTestCode('''
int f() {
int i = b;
return i;
}
''');
await assertHasFix('''
int f(int b) {
int i = b;
return i;
}
''');
}

Future<void> test_with_constructor() async {
await resolveTestCode('''
class A {
A() {
int i = b;
g(i);
}
}
void g(int n) {}
''');
await assertHasFix('''
class A {
A(int b) {
int i = b;
g(i);
}
}
void g(int n) {}
''');
}

Future<void> test_with_local_function() async {
await resolveTestCode('''
void f(int a) {
int g(int a){
int i = b2;
return i;
}
g(3);
}
''');
await assertHasFix('''
void f(int a) {
int g(int a, int b2){
int i = b2;
return i;
}
g(3);
}
''');
}

Future<void> test_with_named() async {
await resolveTestCode('''
int f({int? b}) {
int i = b2;
return i;
}
''');
await assertHasFix('''
int f({int? b, required int b2}) {
int i = b2;
return i;
}
''');
}

Future<void> test_with_named_and_positional() async {
await resolveTestCode('''
int f(int a, {int? b}) {
int i = b2;
return i;
}
''');
await assertHasFix('''
int f(int a, int b2, {int? b}) {
int i = b2;
return i;
}
''');
}

Future<void> test_with_named_nullable() async {
await resolveTestCode('''
int? f({int? b}) {
int? i = b2;
return i;
}
''');
await assertHasFix('''
int? f({int? b, int? b2}) {
int? i = b2;
return i;
}
''');
}

Future<void> test_with_optional_positional() async {
await resolveTestCode('''
int f([int? b]) {
int i = b2;
return i;
}
''');
await assertHasFix('''
int f(int b2, [int? b]) {
int i = b2;
return i;
}
''');
}

Future<void> test_with_required_positional_and_optional() async {
await resolveTestCode('''
int f(int a, [int? b]) {
int i = b2;
return i;
}
''');
await assertHasFix('''
int f(int a, int b2, [int? b]) {
int i = b2;
return i;
}
''');
}

Future<void> test_with_required_positional_and_optional_trailing() async {
await resolveTestCode('''
int f(
int a, [
int? b,
]) {
int i = b2;
return i;
}
''');
await assertHasFix('''
int f(
int a,
int b2, [
int? b,
]) {
int i = b2;
return i;
}
''');
}
}
Loading

0 comments on commit 798cbcf

Please sign in to comment.