Files
sdk/pkg/analysis_server/test/lsp/format_test.dart
Danny Tuppeny ff5706e6c8 [analysis_server] Exclude the enum body semicolon from format-on-type
`EnumBody.semicolon` is the semicolon between the constants and other members. Since the formatter removes this semicolon if there's nothing after it, we can't have it trigger formatting or it will just immediately delete the semicolon you typed.

Fixes https://github.com/dart-lang/sdk/issues/61909
Fixes https://github.com/dart-lang/dart_style/issues/1782
Fixes https://github.com/Dart-Code/Dart-Code/issues/5602

Change-Id: I52bf113b832446da1b672d949d0fc3c2f6d018bf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/466101
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2025-12-04 09:28:07 -08:00

1098 lines
28 KiB
Dart

// 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 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analyzer/src/test_utilities/platform.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 '../tool/lsp_spec/matchers.dart';
import '../utils/matchers.dart';
import '../utils/test_code_extensions.dart';
import 'server_abstract.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(FormatTest);
});
}
@reflectiveTest
class FormatTest extends AbstractLspAnalysisServerTest {
/// Some sample code that is over 50 characters and will be wrapped if the
/// page width is set to 40.
static const codeThatWrapsAt40 =
"var a = ' 10 20 30 40';";
/// Verifies the result of a format-on-type request for the character in the
/// marked range in [content].
///
/// If the marked range has a position, it will be used for the request,
/// otherwise the end point of the range will be used.
Future<List<TextEdit>?> expectFormatOnTypeResult(
String content,
Matcher matcher,
) async {
var code = TestCode.parseNormalized(content);
// We use range to make it really explicit which character is typed and not
// have to worry about whether it's before or after the ^.
expect(code.ranges, hasLength(1));
expect(code.positions, hasLength(anyOf(0, 1)));
var typedCharacter = code.code.substring(
code.range.sourceRange.offset,
code.range.sourceRange.end,
);
await initialize();
await openFile(mainFileUri, code.code);
var result = await formatOnType(
mainFileUri,
code.positions.isNotEmpty ? code.position.position : code.range.range.end,
typedCharacter,
);
expect(
result,
matcher,
reason:
'Typing the character "$typedCharacter" at the marked location '
'should match the supplied matcher',
);
return result;
}
Future<List<TextEdit>> expectFormattedContents(
Uri uri,
String original,
String expected,
) async {
var formatEdits = (await formatDocument(uri))!;
var formattedContents = applyTextEdits(original, formatEdits);
expect(formattedContents, equalsNormalized(expected));
return formatEdits;
}
Future<List<TextEdit>> expectRangeFormattedContents(
Uri uri,
TestCode code,
String expected,
) async {
var formatEdits = (await formatRange(uri, code.range.range))!;
var formattedContents = applyTextEdits(code.code, formatEdits);
expect(formattedContents, equalsNormalized(expected));
return formatEdits;
}
Future<String> formatContents(Uri uri, String original) async {
var formatEdits = (await formatDocument(uri))!;
return applyTextEdits(original, formatEdits);
}
Future<void> test_alreadyFormatted() async {
var code = TestCode.parseNormalized('''
void f() {
print('test');
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await formatDocument(mainFileUri);
expect(formatEdits, isNull);
}
/// Formatting a file with '\r\n' and then a file with '\n' should not result
/// in '\r' being added to the second file.
Future<void> test_changedLineEndings() async {
await provideConfig(
initialize,
// The bug only occurred with an explicit line length because reuse
// of the formatter accidentally required lineLength match the formatters
// (non-null) line length.
{'lineLength': 80},
);
// First format the doc with '\r\n'.
await openFile(mainFileUri, 'int? a;\r\n');
await formatDocument(mainFileUri);
// Now replace and format with '\n'.
await replaceFile(2, mainFileUri, 'int? a;\n');
var formatEdits = await formatDocument(mainFileUri);
// Expect no edits because this document was already formatted.
// When the bug occurs, we'd see edits to add a '\r'.
expect(formatEdits, isNull);
}
Future<void> test_complex() async {
failTestOnErrorDiagnostic = false;
var code = TestCode.parseNormalized('''
ErrorOr<Pair<A, List<B>>> c(
String d,
List<
Either2<E,
F>>
g, {
h = false,
}) {
}
''');
var expected = '''
ErrorOr<Pair<A, List<B>>> c(String d, List<Either2<E, F>> g, {h = false}) {}
''';
await initialize();
await openFile(mainFileUri, code.code);
await expectFormattedContents(mainFileUri, code.code, expected);
}
/// Ensures we use the same registration ID when unregistering even if the
/// server has regenerated registrations multiple times.
Future<void> test_dynamicRegistration_correctIdAfterMultipleChanges() async {
setDocumentFormattingDynamicRegistration();
setDidChangeConfigurationDynamicRegistration();
var registrations = <Registration>[];
// Provide empty config and collect dynamic registrations during
// initialization.
await provideConfig(
() => monitorDynamicRegistrations(registrations, initialize),
{},
);
Registration? registration(Method method) =>
registrationFor(registrations, method);
// By default, the formatters should have been registered.
expect(registration(Method.textDocument_formatting), isNotNull);
expect(registration(Method.textDocument_onTypeFormatting), isNotNull);
expect(registration(Method.textDocument_rangeFormatting), isNotNull);
// Sending config updates causes the server to rebuild its list of registrations
// which exposes a previous bug where we'd retain newly-built registrations
// that may not have been sent to the client (because they had previously
// been sent), resulting in the wrong ID being used for unregistration.
await updateConfig({'foo1': true});
await updateConfig({'foo1': null});
// They should be unregistered if we change the config to disabled.
await monitorDynamicUnregistrations(
registrations,
() => updateConfig({'enableSdkFormatter': false}),
);
expect(registration(Method.textDocument_formatting), isNull);
expect(registration(Method.textDocument_onTypeFormatting), isNull);
expect(registration(Method.textDocument_rangeFormatting), isNull);
}
Future<void> test_dynamicRegistration_forConfiguration() async {
setDocumentFormattingDynamicRegistration();
setDidChangeConfigurationDynamicRegistration();
var registrations = <Registration>[];
// Provide empty config and collect dynamic registrations during
// initialization.
await provideConfig(
() => monitorDynamicRegistrations(registrations, initialize),
{},
);
Registration? registration(Method method) =>
registrationFor(registrations, method);
// By default, the formatters should have been registered.
expect(registration(Method.textDocument_formatting), isNotNull);
expect(registration(Method.textDocument_onTypeFormatting), isNotNull);
expect(registration(Method.textDocument_rangeFormatting), isNotNull);
// They should be unregistered if we change the config to disabled.
await monitorDynamicUnregistrations(
registrations,
() => updateConfig({'enableSdkFormatter': false}),
);
expect(registration(Method.textDocument_formatting), isNull);
expect(registration(Method.textDocument_onTypeFormatting), isNull);
expect(registration(Method.textDocument_rangeFormatting), isNull);
// They should be reregistered if we change the config to enabled.
await monitorDynamicRegistrations(
registrations,
() => updateConfig({'enableSdkFormatter': true}),
);
expect(registration(Method.textDocument_formatting), isNotNull);
expect(registration(Method.textDocument_onTypeFormatting), isNotNull);
expect(registration(Method.textDocument_rangeFormatting), isNotNull);
}
Future<void> test_formatOnType_bad_brace_inComment() async {
await expectFormatOnTypeResult('''
void f ()
{
// hello[!}!]
print('test');
}
''', isNull);
}
Future<void> test_formatOnType_bad_brace_inString() async {
await expectFormatOnTypeResult('''
void f ()
{
print('te[!}!]st');
}
''', isNull);
}
/// Ensure the semicolon after enum constants does not trigger formatting
/// because the formatter removes it if there is nothing after it.
///
/// https://github.com/dart-lang/sdk/issues/61909
Future<void> test_formatOnType_bad_semiColon_enumBodySeparator() async {
await expectFormatOnTypeResult('''
enum E { a, b[!;!] }
''', isNull);
}
Future<void> test_formatOnType_bad_semicolon_inComment() async {
await expectFormatOnTypeResult('''
void f ()
{
// hello[!;!]
print('test');
}
''', isNull);
}
Future<void> test_formatOnType_bad_semicolon_inString() async {
await expectFormatOnTypeResult('''
void f ()
{
print('te[!;!]st');
}
''', isNull);
}
Future<void> test_formatOnType_good_brace_blockEnd() async {
await expectFormatOnTypeResult('''
void f ()
{
print('test');
[!}!]
''', isNotNull);
}
Future<void> test_formatOnType_good_formatsCode() async {
var code = TestCode.parseNormalized('''
void f ()
{
print('test');
[!}!]
''');
var expected = '''void f() {
print('test');
}
''';
var formatEdits = (await expectFormatOnTypeResult(
code.markedCode,
isNotNull,
))!;
var formattedContents = applyTextEdits(code.code, formatEdits);
expect(formattedContents, equalsNormalized(expected));
}
Future<void> test_formatOnType_good_positionAtEnd() async {
await expectFormatOnTypeResult('''
void f ()
{
[!}^!]
''', isNotNull);
}
Future<void> test_formatOnType_good_positionAtOffset() async {
await expectFormatOnTypeResult('''
void f ()
{
[!^}!]
''', isNotNull);
}
Future<void> test_formatOnType_good_semiColon_enumConstructor() async {
await expectFormatOnTypeResult('''
enum X {
a,
b;
const X()[!;!]
}
''', isNotNull);
}
Future<void> test_formatOnType_good_semicolon_statementEnd() async {
await expectFormatOnTypeResult('''
void f ()
{
print('test')[!;!]
}
''', isNotNull);
}
Future<void> test_formatRange_editsOverlapRange() async {
// Only ranges that are fully contained by the range should be applied,
// not those that intersect the start/end.
var code = TestCode.parseNormalized('''
void f()
{
[! print('test');
print('test');
!] print('test');
}
''');
var expected = '''
void f()
{
print('test');
print('test');
print('test');
}
''';
await initialize();
await openFile(mainFileUri, code.code);
await expectRangeFormattedContents(mainFileUri, code, expected);
}
Future<void> test_formatRange_expandsLeadingWhitespaceToNearestLine() async {
var code = TestCode.parseNormalized('''
void f()
{
[! print('test'); // line 2
print('test'); // line 3
print('test'); // line 4!]
}
''');
const expected = '''
void f()
{
print('test'); // line 2
print('test'); // line 3
print('test'); // line 4
}
''';
await initialize();
await openFile(mainFileUri, code.code);
await expectRangeFormattedContents(mainFileUri, code, expected);
}
Future<void> test_formatRange_invalidRange() async {
var code = TestCode.parseNormalized('''
void f()
{
print('test');
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var formatRangeRequest = formatRange(
mainFileUri,
Range(
start: Position(line: 0, character: 0),
end: Position(line: 10000, character: 0),
),
);
await expectLater(
formatRangeRequest,
throwsA(isResponseError(ServerErrorCodes.invalidFileLineCol)),
);
}
Future<void> test_formatRange_simple() async {
var code = TestCode.parseNormalized('''
main ()
{
print('test');
}
[!main2 ()
{
print('test');
}!]
main3 ()
{
print('test');
}
''');
var expected = '''
main ()
{
print('test');
}
main2() {
print('test');
}
main3 ()
{
print('test');
}
''';
await initialize();
await openFile(mainFileUri, code.code);
await expectRangeFormattedContents(mainFileUri, code, expected);
}
Future<void> test_formatRange_trailingNewline_47702() async {
// Check we complete when a formatted block ends with a newline.
// https://github.com/dart-lang/sdk/issues/47702
var code = TestCode.parseNormalized('''
int? a;
[!
int? b;
!]
''');
var expected = '''
int? a;
int? b;
''';
await initialize();
await openFile(mainFileUri, code.code);
await expectRangeFormattedContents(mainFileUri, code, expected);
}
Future<void> test_invalidSyntax() async {
failTestOnErrorDiagnostic = false;
var code = TestCode.parseNormalized('''
void f(((( {
print('test');
}
''');
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await formatDocument(mainFileUri);
expect(formatEdits, isNull);
}
Future<void> test_lineLength() async {
var code = TestCode.parseNormalized('''
void f() =>
print(
'123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789'
);
''');
var expectedDefault = '''
void f() => print(
'123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789',
);
''';
var expectedLongLines = '''
void f() => print('123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789');
''';
// Initialize with config support, supplying an empty config when requested.
await provideConfig(
initialize,
{}, // empty config
);
await openFile(mainFileUri, code.code);
await expectFormattedContents(mainFileUri, code.code, expectedDefault);
await updateConfig({'lineLength': 500});
await expectFormattedContents(mainFileUri, code.code, expectedLongLines);
}
Future<void> test_lineLength_analysisOptions() async {
const codeContent = codeThatWrapsAt40;
const optionsContent = '''
formatter:
page_width: 40
''';
newFile(analysisOptionsPath, optionsContent);
newFile(mainFilePath, codeContent);
await initialize();
// Ignore trailing newlines when checking for wrapping.
var formatted = await formatContents(mainFileUri, codeContent);
expect(formatted.trim(), contains('\n'));
}
Future<void> test_lineLength_analysisOptions_nestedEmpty() async {
const codeContent = codeThatWrapsAt40;
const optionsContent = '''
formatter:
page_width: 40
''';
var nestedAnalysisOptionsPath = join(
projectFolderPath,
'lib',
'analysis_options.yaml',
);
newFile(analysisOptionsPath, optionsContent);
newFile(
nestedAnalysisOptionsPath,
'# empty',
); // suppress the parent options.
newFile(mainFilePath, codeContent);
await initialize();
// Ignore trailing newlines when checking for wrapping.
var formatted = await formatContents(mainFileUri, codeContent);
expect(formatted.trim(), isNot(contains('\n')));
}
Future<void> test_lineLength_analysisOptions_nestedIncludes() async {
const codeContent = codeThatWrapsAt40;
const optionsContent = '''
formatter:
page_width: 40
''';
var nestedAnalysisOptionsPath = join(
projectFolderPath,
'lib',
'analysis_options.yaml',
);
newFile(analysisOptionsPath, optionsContent);
newFile(nestedAnalysisOptionsPath, 'include: ../analysis_options.yaml');
newFile(mainFilePath, codeContent);
await initialize();
// Ignore trailing newlines when checking for wrapping.
var formatted = await formatContents(mainFileUri, codeContent);
expect(formatted.trim(), contains('\n'));
}
Future<void> test_lineLength_analysisOptions_overridesConfig() async {
const codeContent = codeThatWrapsAt40;
const optionsContent = '''
formatter:
page_width: 40
''';
newFile(analysisOptionsPath, optionsContent);
newFile(mainFilePath, codeContent);
await provideConfig(initialize, {
// This won't apply because analysis_options wins.
'lineLength': 100,
});
// Ignore trailing newlines when checking for wrapping.
var formatted = await formatContents(mainFileUri, codeContent);
expect(formatted.trim(), contains('\n'));
}
Future<void> test_lineLength_outsideWorkspaceFolders() async {
var code = TestCode.parseNormalized('''
void f() {
print('123456789 ''123456789 ''123456789 ');
}
''');
const expectedContents = '''
void f() {
print(
'123456789 '
'123456789 '
'123456789 ',
);
}
''';
await provideConfig(
() => initialize(
// Use empty roots so the test file is not inside any known
// WorkspaceFolder.
allowEmptyRootUri: true,
),
// Global config (this should be used).
{'lineLength': 10},
);
await openFile(mainFileUri, code.code);
await expectFormattedContents(mainFileUri, code.code, expectedContents);
}
Future<void> test_lineLength_workspaceFolderSpecified() async {
var code = TestCode.parseNormalized('''
void f() {
print('123456789 ''123456789 ''123456789 ');
}
''');
const expectedContents = '''
void f() {
print(
'123456789 '
'123456789 '
'123456789 ',
);
}
''';
await provideConfig(
initialize,
// Global config.
{'lineLength': 200},
folderConfig: {
// WorkspaceFolder config for this project (this should be used).
projectFolderPath: {'lineLength': 10},
},
);
await openFile(mainFileUri, code.code);
await expectFormattedContents(mainFileUri, code.code, expectedContents);
}
Future<void> test_lineLength_workspaceFolderUnspecified() async {
var code = TestCode.parseNormalized('''
void f() {
print('123456789 ''123456789 ''123456789 ');
}
''');
const expectedContents = '''
void f() {
print(
'123456789 '
'123456789 '
'123456789 ',
);
}
''';
await provideConfig(
initialize,
// Global config (this should be used).
{'lineLength': 10},
folderConfig: {
// WorkspaceFolder config for this project that doesn't specific
// lineLength.
projectFolderPath: {'someOtherValue': 'foo'},
},
);
await openFile(mainFileUri, code.code);
await expectFormattedContents(mainFileUri, code.code, expectedContents);
}
Future<void> test_minimalEdits_addWhitespace() async {
// Check we only get one edit to add the required whitespace and not
// an entire document replacement.
var code = TestCode.parseNormalized('''
void f(){}
''');
const expected = '''
void f() {}
''';
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await expectFormattedContents(
mainFileUri,
code.code,
expected,
);
expect(formatEdits, hasLength(1));
expect(formatEdits[0].newText, ' ');
expect(formatEdits[0].range.start, equals(Position(line: 0, character: 8)));
}
Future<void> test_minimalEdits_removeFileLeadingWhitespace() async {
// Check whitespace before the first token is handled.
var code = TestCode.parseNormalized('''
void f() {}
''');
const expected = '''
void f() {}
''';
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await expectFormattedContents(
mainFileUri,
code.code,
expected,
);
expect(formatEdits, hasLength(1));
expect(formatEdits[0].newText, '');
expect(formatEdits[0].range.start, equals(Position(line: 0, character: 0)));
expect(formatEdits[0].range.end, equals(Position(line: 3, character: 0)));
}
Future<void> test_minimalEdits_removeFileTrailingWhitespace() async {
// Check whitespace after the last token is handled.
var code = TestCode.parseNormalized('''
void f() {}
''');
const expected = '''
void f() {}
''';
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await expectFormattedContents(
mainFileUri,
code.code,
expected,
);
expect(formatEdits, hasLength(1));
expect(formatEdits[0].newText, '');
expect(formatEdits[0].range.start, equals(Position(line: 1, character: 0)));
expect(formatEdits[0].range.end, equals(Position(line: 5, character: 0)));
}
Future<void> test_minimalEdits_removePartialWhitespaceAfter() async {
// Check we get an edit only to remove the unnecessary trailing whitespace
// and not to replace the whole whitespace with a single space.
var code = TestCode.parseNormalized('''
void f() {}
''');
const expected = '''
void f() {}
''';
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await expectFormattedContents(
mainFileUri,
code.code,
expected,
);
expect(formatEdits, hasLength(1));
expect(
formatEdits[0],
equals(
TextEdit(
range: Range(
start: Position(line: 0, character: 9),
end: Position(line: 0, character: 15),
),
newText: '',
),
),
);
}
Future<void> test_minimalEdits_removePartialWhitespaceBefore() async {
// Check we get an edit only to remove the unnecessary leading whitespace
// and not to replace the whole whitespace with a single space.
var code = TestCode.parseNormalized('''
void f()
{}
''');
const expected = '''
void f() {}
''';
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await expectFormattedContents(
mainFileUri,
code.code,
expected,
);
expect(formatEdits, hasLength(1));
expect(
formatEdits[0],
equals(
TextEdit(
range: Range(
start: Position(line: 0, character: 8),
end: Position(line: 3, character: 0),
),
newText: '',
),
),
);
}
Future<void> test_minimalEdits_removeWhitespace() async {
// Check we only get two edits to remove the unwanted whitespace and not
// an entire document replacement.
var code = TestCode.parseNormalized('''
void f( ) { }
''');
const expected = '''
void f() {}
''';
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await expectFormattedContents(
mainFileUri,
code.code,
expected,
);
expect(formatEdits, hasLength(2));
expect(formatEdits[0].newText, isEmpty);
expect(formatEdits[0].range.start, equals(Position(line: 0, character: 7)));
expect(formatEdits[1].newText, isEmpty);
expect(
formatEdits[1].range.start,
equals(Position(line: 0, character: 11)),
);
}
Future<void> test_minimalEdits_withComments() async {
// Check we can get edits that span a comment (which does not appear in the
// main token list).
var code = TestCode.parseNormalized('''
void f() {
var a = 1;
// Comment
print(a);
}
''');
const expected = '''
void f() {
var a = 1;
// Comment
print(a);
}
''';
await initialize();
await openFile(mainFileUri, code.code);
var formatEdits = await expectFormattedContents(
mainFileUri,
code.code,
expected,
);
expect(formatEdits, hasLength(3));
expect(
formatEdits[0],
equals(
TextEdit(
range: Range(
start: Position(line: 1, character: 2),
end: Position(line: 1, character: 8),
),
newText: '',
),
),
);
expect(
formatEdits[1],
equals(
TextEdit(
range: Range(
start: Position(line: 2, character: 2),
end: Position(line: 2, character: 8),
),
newText: '',
),
),
);
expect(
formatEdits[2],
equals(
TextEdit(
range: Range(
start: Position(line: 3, character: 2),
end: Position(line: 3, character: 8),
),
newText: '',
),
),
);
}
Future<void> test_nonDartFile() async {
await initialize();
await openFile(pubspecFileUri, simplePubspecContent);
var formatEdits = await formatOnType(pubspecFileUri, startOfDocPos, '}');
expect(formatEdits, isNull);
}
Future<void> test_path_doesNotExist() async {
await initialize();
await expectLater(
formatDocument(toUri(join(projectFolderPath, 'missing.dart'))),
throwsA(
isResponseError(
ServerErrorCodes.invalidFilePath,
message: 'File does not exist',
),
),
);
}
Future<void> test_path_invalidFormat() async {
await initialize();
await expectLater(
formatDocument(
// Add some invalid path characters to the end of a valid file:// URI.
Uri.parse(mainFileUri.toString() + r'###***\\\///:::.dart'),
),
throwsA(
isResponseError(
ServerErrorCodes.invalidFilePath,
message: 'URI does not contain a valid file path',
),
),
);
}
Future<void> test_path_notFileScheme() async {
await initialize();
await expectLater(
formatDocument(Uri.parse('a:/a.dart')),
throwsA(
isResponseError(
ServerErrorCodes.invalidFilePath,
message:
"URI scheme 'a' is not supported. Allowed schemes are 'file'.",
),
),
);
}
Future<void> test_simple() async {
var code = TestCode.parseNormalized('''
void f ()
{
print('test');
}
''');
var expected = '''
void f() {
print('test');
}
''';
await initialize();
await openFile(mainFileUri, code.code);
await expectFormattedContents(mainFileUri, code.code, expected);
}
Future<void> test_trailingCommas_automate() async {
const optionsContent = '''
formatter:
trailing_commas: automate
''';
const initialContent = '''
enum A {
a,
b,
}
''';
const expectedContent = '''
enum A { a, b }
''';
newFile(analysisOptionsPath, optionsContent);
newFile(mainFilePath, initialContent);
await initialize();
// Ignore trailing newlines when checking for wrapping.
var formatted = await formatContents(mainFileUri, initialContent);
expect(formatted.trim(), expectedContent.trim());
}
Future<void> test_trailingCommas_preserve() async {
var optionsContent = normalizeNewlinesForPlatform('''
formatter:
trailing_commas: preserve
''');
var initialContent = normalizeNewlinesForPlatform('''
enum A { a, b, }
''');
var expectedContent = normalizeNewlinesForPlatform('''
enum A {
a,
b,
}
''');
newFile(analysisOptionsPath, optionsContent);
newFile(mainFilePath, initialContent);
await initialize();
// Ignore trailing newlines when checking for wrapping.
var formatted = await formatContents(mainFileUri, initialContent);
expect(formatted.trim(), expectedContent.trim());
}
Future<void> test_trailingCommas_unspecified() async {
const initialContent = '''
enum A {
a,
b,
}
''';
const expectedContent = '''
enum A { a, b }
''';
newFile(mainFilePath, initialContent);
await initialize();
// Ignore trailing newlines when checking for wrapping.
var formatted = await formatContents(mainFileUri, initialContent);
expect(formatted.trim(), expectedContent.trim());
}
Future<void> test_unopenFile() async {
var code = TestCode.parseNormalized('''
void f ()
{
print('test');
}
''');
var expected = '''
void f() {
print('test');
}
''';
newFile(mainFilePath, code.code);
await initialize();
await expectFormattedContents(mainFileUri, code.code, expected);
}
Future<void> test_validSyntax_withErrors() async {
failTestOnErrorDiagnostic = false;
// We should still be able to format syntactically valid code even if it has
// analysis errors.
var code = TestCode.parseNormalized('''
void f() {
print(unresolved);
}
''');
const expected = '''
void f() {
print(unresolved);
}
''';
await initialize();
await openFile(mainFileUri, code.code);
await expectFormattedContents(mainFileUri, code.code, expected);
}
}