| // Copyright (c) 2015, 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/src/protocol_server.dart'; | |
| import 'package:analyzer/source/source_range.dart'; | |
| import 'package:analyzer/src/test_utilities/test_code_format.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; | |
| import '../analysis_server_base.dart'; | |
| import '../mocks.dart'; | |
| void main() { | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(FormatTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class FormatTest extends PubPackageAnalysisServerTest { | |
| @override | |
| Future<void> setUp() async { | |
| super.setUp(); | |
| await setRoots(included: [workspaceRootPath], excluded: []); | |
| } | |
| /// Verify that an overlay change is reflected in a format request that | |
| /// is sent immediately without waiting. | |
| /// | |
| /// https://github.com/dart-lang/sdk/issues/57120 | |
| Future<void> test_format_immediatelyAfterOverlayChange() async { | |
| var initialContentNeedsFormatting = 'void main() { }'; | |
| var updatedContentNeedsNoFormatting = | |
| "void main() {\n print('hello world');\n}\n"; | |
| // Set the initial content to something that will produce format edits. | |
| await handleSuccessfulRequest( | |
| AnalysisUpdateContentParams({ | |
| testFile.path: AddContentOverlay(initialContentNeedsFormatting), | |
| }).toRequest('1', clientUriConverter: server.uriConverter), | |
| ); | |
| // Update the content to something that will not produce edits, but do not | |
| // await, because we are testing that the server is consistent even if it | |
| // doesn't have time to handle the change. | |
| unawaited( | |
| handleSuccessfulRequest( | |
| AnalysisUpdateContentParams({ | |
| testFile.path: AddContentOverlay(updatedContentNeedsNoFormatting), | |
| }).toRequest('2', clientUriConverter: server.uriConverter), | |
| ), | |
| ); | |
| var formatResult = await _format(); | |
| // Expect no edits, because the last overlay was already formatted. | |
| expect(formatResult.edits, hasLength(0)); | |
| } | |
| Future<void> test_format_longLine_analysisOptions() async { | |
| writeTestPackageAnalysisOptionsFile(r''' | |
| formatter: | |
| page_width: 100 | |
| '''); | |
| await _expectNoFormatting(''' | |
| fun(firstParam, secondParam, thirdParam, fourthParam) { | |
| if (firstParam.noNull && secondParam.noNull && thirdParam.noNull && fourthParam.noNull) {} | |
| } | |
| '''); | |
| } | |
| Future<void> test_format_longLine_analysisOptions_overridesParameter() async { | |
| writeTestPackageAnalysisOptionsFile(r''' | |
| formatter: | |
| page_width: 100 | |
| '''); | |
| await _expectNoFormatting(''' | |
| fun(firstParam, secondParam, thirdParam, fourthParam) { | |
| if (firstParam.noNull && secondParam.noNull && thirdParam.noNull && fourthParam.noNull) {} | |
| } | |
| ''', lineLength: 50); | |
| } | |
| Future<void> test_format_longLine_parameter() async { | |
| await _expectNoFormatting(''' | |
| fun(firstParam, secondParam, thirdParam, fourthParam) { | |
| if (firstParam.noNull && secondParam.noNull && thirdParam.noNull && fourthParam.noNull) {} | |
| } | |
| ''', lineLength: 100); | |
| } | |
| Future<void> test_format_noOp() async { | |
| // Already formatted source | |
| await _expectNoFormatting(''' | |
| void f() { | |
| int x = 3; | |
| } | |
| '''); | |
| } | |
| Future<void> test_format_noSelection() async { | |
| var content = ''' | |
| void f() { int x = 3; } | |
| '''; | |
| var expected = ''' | |
| void f() { | |
| int x = 3; | |
| } | |
| '''; | |
| await _expectFormatted(content, expected); | |
| } | |
| Future<void> test_format_simple() async { | |
| var content = ''' | |
| void f() { [!int!] x = 3; } | |
| '''; | |
| var expected = ''' | |
| void f() { | |
| [!int!] x = 3; | |
| } | |
| '''; | |
| await _expectFormatted(content, expected); | |
| } | |
| Future<void> test_format_trailingCommas_automate() async { | |
| writeTestPackageAnalysisOptionsFile(r''' | |
| formatter: | |
| trailing_commas: automate | |
| '''); | |
| var content = ''' | |
| enum A { | |
| a, | |
| b, | |
| } | |
| '''; | |
| var expected = ''' | |
| enum A { a, b } | |
| '''; | |
| await _expectFormatted(content, expected); | |
| } | |
| Future<void> test_format_trailingCommas_preserve() async { | |
| writeTestPackageAnalysisOptionsFile(r''' | |
| formatter: | |
| trailing_commas: preserve | |
| '''); | |
| var content = ''' | |
| enum A { a, b, } | |
| '''; | |
| var expected = ''' | |
| enum A { | |
| a, | |
| b, | |
| } | |
| '''; | |
| await _expectFormatted(content, expected); | |
| } | |
| Future<void> test_format_trailingCommas_unspecified() async { | |
| var content = ''' | |
| enum A { | |
| a, | |
| b, | |
| } | |
| '''; | |
| var expected = ''' | |
| enum A { a, b } | |
| '''; | |
| await _expectFormatted(content, expected); | |
| } | |
| /// Verify version 2.19 is passed to the formatter so it will not produce any | |
| /// edits for code containing records (since it fails to parse). | |
| Future<void> test_format_version_2_19() async { | |
| writeTestPackageConfig2(languageVersion: '2.19'); | |
| await _expectFormatError(''' | |
| var a = (1, 2); | |
| '''); | |
| } | |
| /// Verify version 3.0 is passed to the formatter so will produce edits for | |
| /// code containing records. | |
| Future<void> test_format_version_3_0() async { | |
| var content = ''' | |
| var a = (1, 2); | |
| '''; | |
| var expected = ''' | |
| var a = (1, 2); | |
| '''; | |
| await _expectFormatted(content, expected); | |
| } | |
| Future<void> test_format_withErrors() async { | |
| await _expectFormatError(''' | |
| void f() { int x = | |
| '''); | |
| } | |
| Future<void> _expectFormatError(String content) async { | |
| addTestFile(content); | |
| await waitForTasksFinished(); | |
| var request = EditFormatParams( | |
| testFile.path, | |
| 0, | |
| 0, | |
| ).toRequest('0', clientUriConverter: server.uriConverter); | |
| var response = await handleRequest(request); | |
| expect(response, isResponseFailure('0')); | |
| } | |
| Future<void> _expectFormatted(String content, String expectedContent) async { | |
| addTestFile(content); | |
| var selection = parsedTestCode.ranges.isNotEmpty | |
| ? parsedTestCode.range.sourceRange | |
| : SourceRange(0, 0); | |
| var expectedCode = TestCode.parseNormalized(expectedContent); | |
| var expectedSelection = expectedCode.ranges.isNotEmpty | |
| ? expectedCode.range.sourceRange | |
| : SourceRange(0, 0); | |
| await waitForTasksFinished(); | |
| var formatResult = await _format(selection: selection); | |
| expect(formatResult.edits, isNotNull); | |
| expect(formatResult.edits, hasLength(1)); | |
| var edit = formatResult.edits[0]; | |
| expect(edit.replacement, equals(expectedCode.code)); | |
| expect(formatResult.selectionOffset, equals(expectedSelection.offset)); | |
| expect(formatResult.selectionLength, equals(expectedSelection.length)); | |
| } | |
| Future<void> _expectNoFormatting(String content, {int? lineLength}) async { | |
| addTestFile(content); | |
| await waitForTasksFinished(); | |
| var selection = parsedRanges.isNotEmpty | |
| ? parsedSourceRange | |
| : SourceRange(0, 0); | |
| var formatResult = await _format( | |
| selection: selection, | |
| lineLength: lineLength, | |
| ); | |
| expect(formatResult.edits, isNotNull); | |
| expect(formatResult.edits, hasLength(0)); | |
| // No change in selection | |
| expect(formatResult.selectionOffset, equals(selection.offset)); | |
| expect(formatResult.selectionLength, equals(selection.length)); | |
| } | |
| Future<EditFormatResult> _format({ | |
| SourceRange? selection, | |
| int? lineLength, | |
| }) async { | |
| var request = EditFormatParams( | |
| testFile.path, | |
| selection?.offset ?? 0, | |
| selection?.length ?? 0, | |
| lineLength: lineLength, | |
| ).toRequest('0', clientUriConverter: server.uriConverter); | |
| var response = await handleSuccessfulRequest(request); | |
| return EditFormatResult.fromResponse( | |
| response, | |
| clientUriConverter: server.uriConverter, | |
| ); | |
| } | |
| } |