[analyzer] Support "Inline Method" refactor in LSP
Fixes https://github.com/Dart-Code/Dart-Code/issues/547.
Change-Id: I02f905673820ef22b5b10055f32710d660beaa2c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/197380
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
index 5a8d94b..317b6a8 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
@@ -164,6 +164,11 @@
InlineLocalRefactoring(server.searchEngine, result, offset);
return success(refactor);
+ case RefactoringKind.INLINE_METHOD:
+ final refactor =
+ InlineMethodRefactoring(server.searchEngine, result, offset);
+ return success(refactor);
+
default:
return error(ServerErrorCodes.InvalidCommandArguments,
'Unknown RefactoringKind $kind was supplied to $commandName');
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index 2d786af..322e9d8d 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -409,6 +409,7 @@
}
}
+ // Inlines
if (shouldIncludeKind(CodeActionKind.RefactorInline)) {
// Inline Local Variable
if (InlineLocalRefactoring(server.searchEngine, unit, offset)
@@ -416,6 +417,13 @@
refactorActions.add(createRefactor(CodeActionKind.RefactorInline,
'Inline Local Variable', RefactoringKind.INLINE_LOCAL_VARIABLE));
}
+
+ // Inline Method
+ if (InlineMethodRefactoring(server.searchEngine, unit, offset)
+ .isAvailable()) {
+ refactorActions.add(createRefactor(CodeActionKind.RefactorInline,
+ 'Inline Method', RefactoringKind.INLINE_METHOD));
+ }
}
return refactorActions;
diff --git a/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart b/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
index 47362ca..1cff243 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
@@ -290,6 +290,39 @@
return Future.value(change);
}
+ @override
+ bool isAvailable() {
+ return !_checkOffset().hasFatalError;
+ }
+
+ /// Checks if [offset] is a method that can be inlined.
+ RefactoringStatus _checkOffset() {
+ var fatalStatus = RefactoringStatus.fatal(
+ 'Method declaration or reference must be selected to activate this refactoring.');
+
+ var identifier = NodeLocator(offset).searchWithin(resolveResult.unit);
+ if (identifier is! SimpleIdentifier) {
+ return fatalStatus;
+ }
+ var element = identifier.writeOrReadElement;
+ if (element is! ExecutableElement) {
+ return fatalStatus;
+ }
+ if (element.isSynthetic) {
+ return fatalStatus;
+ }
+ // maybe operator
+ if (element.isOperator) {
+ return RefactoringStatus.fatal('Cannot inline operator.');
+ }
+ // maybe [a]sync*
+ if (element.isGenerator) {
+ return RefactoringStatus.fatal('Cannot inline a generator.');
+ }
+
+ return RefactoringStatus();
+ }
+
_SourcePart _createSourcePart(SourceRange range) {
var source = _methodUtils.getRangeText(range);
var prefix = getLinePrefix(source);
diff --git a/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart b/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart
index 23b8552..cdb3485 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart
@@ -242,6 +242,10 @@
/// The name of the method (or function) being inlined.
String? get methodName;
+
+ /// Return `true` if refactoring is available, possibly without checking all
+ /// initial conditions.
+ bool isAvailable();
}
/// [Refactoring] to move/rename a file.
diff --git a/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart b/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
index 8d87a88..791c627d 100644
--- a/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
@@ -19,6 +19,7 @@
defineReflectiveTests(ExtractWidgetRefactorCodeActionsTest);
defineReflectiveTests(ExtractVariableRefactorCodeActionsTest);
defineReflectiveTests(InlineLocalVariableRefactorCodeActionsTest);
+ defineReflectiveTests(InlineMethodRefactorCodeActionsTest);
});
}
@@ -515,7 +516,7 @@
@reflectiveTest
class InlineLocalVariableRefactorCodeActionsTest
extends AbstractCodeActionsTest {
- final extractVariableTitle = 'Inline Local Variable';
+ final inlineVariableTitle = 'Inline Local Variable';
Future<void> test_appliesCorrectEdits() async {
const content = '''
@@ -539,7 +540,86 @@
final codeActions = await getCodeActions(mainFileUri.toString(),
position: positionFromMarker(content));
final codeAction = findCommand(
- codeActions, Commands.performRefactor, extractVariableTitle)!;
+ codeActions, Commands.performRefactor, inlineVariableTitle)!;
+
+ await verifyCodeActionEdits(
+ codeAction, withoutMarkers(content), expectedContent);
+ }
+}
+
+@reflectiveTest
+class InlineMethodRefactorCodeActionsTest extends AbstractCodeActionsTest {
+ final inlineMethodTitle = 'Inline Method';
+
+ Future<void> test_inlineAtCallSite() async {
+ const content = '''
+void foo1() {
+ ba^r();
+}
+
+void foo2() {
+ bar();
+}
+
+void bar() {
+ print('test');
+}
+ ''';
+ const expectedContent = '''
+void foo1() {
+ print('test');
+}
+
+void foo2() {
+ bar();
+}
+
+void bar() {
+ print('test');
+}
+ ''';
+ newFile(mainFilePath, content: withoutMarkers(content));
+ await initialize();
+
+ final codeActions = await getCodeActions(mainFileUri.toString(),
+ position: positionFromMarker(content));
+ final codeAction =
+ findCommand(codeActions, Commands.performRefactor, inlineMethodTitle)!;
+
+ await verifyCodeActionEdits(
+ codeAction, withoutMarkers(content), expectedContent);
+ }
+
+ Future<void> test_inlineAtMethod() async {
+ const content = '''
+void foo1() {
+ bar();
+}
+
+void foo2() {
+ bar();
+}
+
+void ba^r() {
+ print('test');
+}
+ ''';
+ const expectedContent = '''
+void foo1() {
+ print('test');
+}
+
+void foo2() {
+ print('test');
+}
+ ''';
+ newFile(mainFilePath, content: withoutMarkers(content));
+ await initialize();
+
+ final codeActions = await getCodeActions(mainFileUri.toString(),
+ position: positionFromMarker(content));
+ final codeAction =
+ findCommand(codeActions, Commands.performRefactor, inlineMethodTitle)!;
await verifyCodeActionEdits(
codeAction, withoutMarkers(content), expectedContent);