// Copyright (c) 2018, 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 'package:analysis_server/lsp_protocol/protocol.dart'; import 'package:analysis_server/src/lsp/constants.dart'; import 'package:analysis_server/src/services/correction/fix_internal.dart'; import 'package:linter/src/rules.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import '../shared/shared_code_actions_source_tests.dart'; import 'code_actions_mixin.dart'; import 'server_abstract.dart'; void main() { defineReflectiveSuite(() { defineReflectiveTests(SourceCodeActionsTest); defineReflectiveTests(SortMembersSourceCodeActionsTest); defineReflectiveTests(OrganizeImportsSourceCodeActionsTest); defineReflectiveTests(FixAllSourceCodeActionsTest); }); } abstract class AbstractSourceCodeActionsTest extends AbstractLspAnalysisServerTest with LspSharedTestMixin, SharedSourceCodeActionsTestMixin, CodeActionsTestMixin {} @reflectiveTest class FixAllSourceCodeActionsTest extends AbstractSourceCodeActionsTest { @override Future setUp() async { await super.setUp(); // Fix tests are likely to have diagnostics that need fixing. failTestOnErrorDiagnostic = false; registerBuiltInFixGenerators(); } Future test_appliesCorrectEdits() async { const analysisOptionsContent = ''' linter: rules: - unnecessary_new - prefer_collection_literals '''; const content = ''' final a = new Object(); final b = new Set(); '''; const expectedContent = ''' final a = Object(); final b = {}; '''; registerLintRules(); newFile(analysisOptionsPath, analysisOptionsContent); newFile(mainFilePath, content); await verifyCodeActionLiteralEdits( content, expectedContent, command: Commands.fixAll, ); } Future test_multipleIterations_noOverlay() async { const analysisOptionsContent = ''' linter: rules: - prefer_final_locals - prefer_const_declarations '''; const content = ''' void f() { var a = 'test'; } '''; const expectedContent = ''' void f() { const a = 'test'; } '''; registerLintRules(); newFile(analysisOptionsPath, analysisOptionsContent); newFile(mainFilePath, content); await verifyCodeActionLiteralEdits( content, expectedContent, command: Commands.fixAll, ); } Future test_multipleIterations_overlay() async { const analysisOptionsContent = ''' linter: rules: - prefer_final_locals - prefer_const_declarations '''; const content = ''' void f() { var a = 'test'; } '''; const expectedContent = ''' void f() { const a = 'test'; } '''; registerLintRules(); newFile(analysisOptionsPath, analysisOptionsContent); await verifyCodeActionLiteralEdits( content, expectedContent, command: Commands.fixAll, ); } Future test_multipleIterations_withClientModification() async { const analysisOptionsContent = ''' linter: rules: - prefer_final_locals - prefer_const_declarations '''; const content = ''' void f() { var a = 'test'; } '''; registerLintRules(); newFile(analysisOptionsPath, analysisOptionsContent); var codeAction = await expectCodeActionLiteral( content, command: Commands.fixAll, ); var command = codeAction.command!; // Files must be open to apply edits. await openFile(testFileUri, content); // Execute the command with a modification and capture the edit that is // sent back to us. ApplyWorkspaceEditParams? editParams; await handleExpectedRequest< Object?, ApplyWorkspaceEditParams, ApplyWorkspaceEditResult >( Method.workspace_applyEdit, ApplyWorkspaceEditParams.fromJson, () async { // Apply the command and immediately modify a file afterwards. var commandFuture = executeCommand(command); await commandFuture; await replaceFile(12345, testFileUri, 'client-modified-content'); return commandFuture; }, handler: (edit) { // When the server sends the edit back, just keep a copy and say we // applied successfully (we'll verify the actual edit below). editParams = edit; return ApplyWorkspaceEditResult(applied: true); }, ); // Extract the text edit from the 'workspace/applyEdit' params the server // sent us. var change = editParams?.edit.documentChanges!.single; var edit = change!.map( (create) => throw 'Expected edit, got create', (delete) => throw 'Expected edit, got delete', (rename) => throw 'Expected edit, got rename', (edit) => edit, ); // Ensure the edit says that it was based on version 1 (the original // version) and not the updated 12345 version we sent. expect(edit.textDocument.version, 1); } Future test_part() async { var containerFilePath = join(projectFolderPath, 'lib', 'container.dart'); var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); const analysisOptionsContent = ''' linter: rules: - unnecessary_new - prefer_collection_literals '''; const containerFileContent = ''' part 'part.dart'; '''; const content = ''' part of 'container.dart'; final a = new Object(); final b = new Set(); '''; const expectedContent = ''' part of 'container.dart'; final a = Object(); final b = {}; '''; registerLintRules(); newFile(analysisOptionsPath, analysisOptionsContent); newFile(containerFilePath, containerFileContent); newFile(partFilePath, content); await verifyCodeActionLiteralEdits( filePath: partFilePath, content, expectedContent, command: Commands.fixAll, ); } Future test_privateUnusedParameters_notRemovedIfSave() async { const content = ''' class _MyClass { int? _param; _MyClass({ this._param, }); } '''; var codeAction = await expectCodeActionLiteral( content, command: Commands.fixAll, triggerKind: CodeActionTriggerKind.Automatic, ); var command = codeAction.command!; // We should not get an applyEdit call during the command execution because // no edits should be produced. var applyEditSubscription = requestsFromServer .where((n) => n.method == Method.workspace_applyEdit) .listen((_) => throw 'workspace/applyEdit was unexpectedly called'); var commandResponse = await executeCommand(command); expect(commandResponse, isNull); await pumpEventQueue(); await applyEditSubscription.cancel(); } Future test_privateUnusedParameters_removedByDefault() async { const content = ''' class _MyClass { int? param; _MyClass({ this.param, }); } '''; const expectedContent = ''' class _MyClass { int? param; _MyClass(); } '''; await verifyCodeActionLiteralEdits( content, expectedContent, command: Commands.fixAll, ); } Future test_unavailable_outsideAnalysisRoot() async { var otherFile = convertPath('/other/file.dart'); var content = ''; await expectNoAction( filePath: otherFile, content, command: Commands.organizeImports, ); } Future test_unusedUsings_notRemovedIfSave() async { const content = ''' import 'dart:async'; int? a; '''; var codeAction = await expectCodeActionLiteral( content, command: Commands.fixAll, triggerKind: CodeActionTriggerKind.Automatic, ); var command = codeAction.command!; // We should not get an applyEdit call during the command execution because // no edits should be produced. var applyEditSubscription = requestsFromServer .where((n) => n.method == Method.workspace_applyEdit) .listen((_) => throw 'workspace/applyEdit was unexpectedly called'); var commandResponse = await executeCommand(command); expect(commandResponse, isNull); await pumpEventQueue(); await applyEditSubscription.cancel(); } Future test_unusedUsings_removedByDefault() async { const content = ''' import 'dart:async'; int? a; '''; const expectedContent = ''' int? a; '''; await verifyCodeActionLiteralEdits( content, expectedContent, command: Commands.fixAll, ); } } @reflectiveTest class OrganizeImportsSourceCodeActionsTest extends AbstractSourceCodeActionsTest with SharedSourceCodeActionsTestMixin, // Tests are defined in a shared mixin. SharedOrganizeImportsSourceCodeActionsTests {} @reflectiveTest class SortMembersSourceCodeActionsTest extends AbstractSourceCodeActionsTest with SharedSourceCodeActionsTestMixin, // Tests are defined in a shared mixin. SharedSortMembersSourceCodeActionsTests {} @reflectiveTest class SourceCodeActionsTest extends AbstractSourceCodeActionsTest with SharedSourceCodeActionsTestMixin, // Tests are defined in a shared mixin. SharedSourceCodeActionsTests {}