forked from flutter/engine
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Convert ChangeArgumentName to a producer
The ChangeArgumentName "fix" is one of a small number that can produce multiple fixes. I'm proposing that we support these by adding objects that can dynamically produce zero or more CorrectionProducers depending on the situation being flagged as an error. Change-Id: I37c06ffefd46f4b8478d127d1b537b3bc840e024 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/148360 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
- Loading branch information
1 parent
7a94d1d
commit 86edfbe
Showing
4 changed files
with
289 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
pkg/analysis_server/lib/src/services/correction/dart/change_argument_name.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// 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/dart/abstract_producer.dart'; | ||
import 'package:analysis_server/src/services/correction/executable_parameters.dart'; | ||
import 'package:analysis_server/src/services/correction/fix.dart'; | ||
import 'package:analysis_server/src/services/correction/levenshtein.dart'; | ||
import 'package:analyzer/dart/ast/ast.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 ChangeArgumentName extends MultiCorrectionProducer { | ||
static const maxDistance = 4; | ||
|
||
@override | ||
Iterable<CorrectionProducer> get producers sync* { | ||
var names = _getNamedParameterNames(); | ||
if (names == null || names.isEmpty) { | ||
return; | ||
} | ||
SimpleIdentifier argumentName = node; | ||
var invalidName = argumentName.name; | ||
for (var proposedName in names) { | ||
var distance = _computeDistance(invalidName, proposedName); | ||
if (distance <= maxDistance) { | ||
// TODO(brianwilkerson) Create a way to use the distance as part of the | ||
// computation of the priority (so that closer names sort first). | ||
yield _ChangeName(argumentName, proposedName); | ||
} | ||
} | ||
} | ||
|
||
int _computeDistance(String current, String proposal) { | ||
if ((current == 'child' && proposal == 'children') || | ||
(current == 'children' && proposal == 'child')) { | ||
// Special case handling for 'child' and 'children' is unnecessary if | ||
// `maxDistance >= 3`, but is included to prevent regression in case the | ||
// value is changed to improve results. | ||
return 1; | ||
} | ||
return levenshtein(current, proposal, maxDistance, caseSensitive: false); | ||
} | ||
|
||
List<String> _getNamedParameterNames() { | ||
var namedExpression = node?.parent?.parent; | ||
if (node is SimpleIdentifier && | ||
namedExpression is NamedExpression && | ||
namedExpression.name == node.parent && | ||
namedExpression.parent is ArgumentList) { | ||
var parameters = ExecutableParameters( | ||
sessionHelper, | ||
namedExpression.parent.parent, | ||
); | ||
return parameters?.namedNames; | ||
} | ||
return null; | ||
} | ||
|
||
/// Return an instance of this class. Used as a tear-off in `FixProcessor`. | ||
static ChangeArgumentName newInstance() => ChangeArgumentName(); | ||
} | ||
|
||
/// A correction processor that can make one of the possible change computed by | ||
/// the [ChangeArgumentName] producer. | ||
class _ChangeName extends CorrectionProducer { | ||
/// The name of the argument being changed. | ||
final SimpleIdentifier argumentName; | ||
|
||
/// The name to which the argument name will be changed. | ||
final String proposedName; | ||
|
||
_ChangeName(this.argumentName, this.proposedName); | ||
|
||
@override | ||
List<Object> get fixArguments => [proposedName]; | ||
|
||
@override | ||
FixKind get fixKind => DartFixKind.CHANGE_ARGUMENT_NAME; | ||
|
||
@override | ||
Future<void> compute(DartChangeBuilder builder) async { | ||
await builder.addFileEdit(file, (builder) { | ||
builder.addSimpleReplacement(range.node(argumentName), proposedName); | ||
}); | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
pkg/analysis_server/lib/src/services/correction/executable_parameters.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright (c) 2014, 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 'dart:async'; | ||
import 'dart:core'; | ||
|
||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/element/element.dart'; | ||
import 'package:analyzer/src/dart/analysis/session_helper.dart'; | ||
|
||
/// [ExecutableElement], its parameters, and operations on them. | ||
class ExecutableParameters { | ||
final AnalysisSessionHelper sessionHelper; | ||
final ExecutableElement executable; | ||
|
||
final List<ParameterElement> required = []; | ||
final List<ParameterElement> optionalPositional = []; | ||
final List<ParameterElement> named = []; | ||
|
||
factory ExecutableParameters( | ||
AnalysisSessionHelper sessionHelper, AstNode invocation) { | ||
Element element; | ||
// This doesn't handle FunctionExpressionInvocation. | ||
if (invocation is Annotation) { | ||
element = invocation.element; | ||
} else if (invocation is InstanceCreationExpression) { | ||
element = invocation.staticElement; | ||
} else if (invocation is MethodInvocation) { | ||
element = invocation.methodName.staticElement; | ||
} else if (invocation is ConstructorReferenceNode) { | ||
element = invocation.staticElement; | ||
} | ||
if (element is ExecutableElement && !element.isSynthetic) { | ||
return ExecutableParameters._(sessionHelper, element); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
ExecutableParameters._(this.sessionHelper, this.executable) { | ||
for (var parameter in executable.parameters) { | ||
if (parameter.isRequiredPositional) { | ||
required.add(parameter); | ||
} else if (parameter.isOptionalPositional) { | ||
optionalPositional.add(parameter); | ||
} else if (parameter.isNamed) { | ||
named.add(parameter); | ||
} | ||
} | ||
} | ||
|
||
/// Return the path of the file in which the executable is declared. | ||
String get file => executable.source.fullName; | ||
|
||
/// Return the names of the named parameters. | ||
List<String> get namedNames { | ||
return named.map((parameter) => parameter.name).toList(); | ||
} | ||
|
||
/// Return the [FormalParameterList] of the [executable], or `nul be found. | ||
Future<FormalParameterList> getParameterList() async { | ||
var result = await sessionHelper.getElementDeclaration(executable); | ||
var targetDeclaration = result.node; | ||
if (targetDeclaration is ConstructorDeclaration) { | ||
return targetDeclaration.parameters; | ||
} else if (targetDeclaration is FunctionDeclaration) { | ||
var function = targetDeclaration.functionExpression; | ||
return function.parameters; | ||
} else if (targetDeclaration is MethodDeclaration) { | ||
return targetDeclaration.parameters; | ||
} | ||
return null; | ||
} | ||
|
||
/// Return the [FormalParameter] of the [element] in [FormalParameterList], | ||
/// or `null` if it can't be found. | ||
Future<FormalParameter> getParameterNode(ParameterElement element) async { | ||
var result = await sessionHelper.getElementDeclaration(element); | ||
var declaration = result.node; | ||
for (var node = declaration; node != null; node = node.parent) { | ||
if (node is FormalParameter && node.parent is FormalParameterList) { | ||
return node; | ||
} | ||
} | ||
return null; | ||
} | ||
} |
Oops, something went wrong.