16d6594346
+ validation + code completion Fixes https://github.com/dart-lang/sdk/issues/60599 Change-Id: I383c888756e29e091c7abf8d4b8df66387714e31 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/424440 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
968 lines
25 KiB
Dart
968 lines
25 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/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/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';";
|
|
|
|
Future<List<TextEdit>> expectFormattedContents(
|
|
Uri uri,
|
|
String original,
|
|
String expected,
|
|
) async {
|
|
var formatEdits = (await formatDocument(uri))!;
|
|
var formattedContents = applyTextEdits(original, formatEdits);
|
|
expect(formattedContents, equals(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, equals(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 {
|
|
const contents = '''
|
|
void f() {
|
|
print('test');
|
|
}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
|
|
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;
|
|
|
|
const contents = '''
|
|
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, contents);
|
|
await expectFormattedContents(mainFileUri, contents, 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_simple() async {
|
|
const contents = '''
|
|
void f ()
|
|
{
|
|
|
|
print('test');
|
|
^}
|
|
''';
|
|
var expected = '''void f() {
|
|
print('test');
|
|
}
|
|
''';
|
|
var code = TestCode.parse(contents);
|
|
await initialize();
|
|
await openFile(mainFileUri, code.code);
|
|
|
|
var formatEdits =
|
|
(await formatOnType(mainFileUri, code.position.position, '}'))!;
|
|
var formattedContents = applyTextEdits(code.code, formatEdits);
|
|
expect(formattedContents, equals(expected));
|
|
}
|
|
|
|
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.
|
|
const contents = '''
|
|
void f()
|
|
{
|
|
[! print('test');
|
|
print('test');
|
|
!] print('test');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f()
|
|
{
|
|
print('test');
|
|
print('test');
|
|
print('test');
|
|
}
|
|
''';
|
|
var code = TestCode.parse(contents);
|
|
await initialize();
|
|
await openFile(mainFileUri, code.code);
|
|
await expectRangeFormattedContents(mainFileUri, code, expected);
|
|
}
|
|
|
|
Future<void> test_formatRange_expandsLeadingWhitespaceToNearestLine() async {
|
|
const contents = '''
|
|
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
|
|
}
|
|
''';
|
|
var code = TestCode.parse(contents);
|
|
await initialize();
|
|
await openFile(mainFileUri, code.code);
|
|
await expectRangeFormattedContents(mainFileUri, code, expected);
|
|
}
|
|
|
|
Future<void> test_formatRange_invalidRange() async {
|
|
const contents = '''
|
|
void f()
|
|
{
|
|
print('test');
|
|
}
|
|
''';
|
|
var code = TestCode.parse(contents);
|
|
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 {
|
|
const contents = '''
|
|
main ()
|
|
{
|
|
|
|
print('test');
|
|
}
|
|
|
|
[!main2 ()
|
|
{
|
|
|
|
print('test');
|
|
}!]
|
|
|
|
main3 ()
|
|
{
|
|
|
|
print('test');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
main ()
|
|
{
|
|
|
|
print('test');
|
|
}
|
|
|
|
main2() {
|
|
print('test');
|
|
}
|
|
|
|
main3 ()
|
|
{
|
|
|
|
print('test');
|
|
}
|
|
''';
|
|
var code = TestCode.parse(contents);
|
|
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
|
|
const contents = '''
|
|
int? a;
|
|
[!
|
|
int? b;
|
|
!]
|
|
''';
|
|
var expected = '''
|
|
int? a;
|
|
|
|
int? b;
|
|
|
|
''';
|
|
var code = TestCode.parse(contents);
|
|
await initialize();
|
|
await openFile(mainFileUri, code.code);
|
|
await expectRangeFormattedContents(mainFileUri, code, expected);
|
|
}
|
|
|
|
Future<void> test_invalidSyntax() async {
|
|
failTestOnErrorDiagnostic = false;
|
|
|
|
const contents = '''
|
|
void f(((( {
|
|
print('test');
|
|
}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
|
|
var formatEdits = await formatDocument(mainFileUri);
|
|
expect(formatEdits, isNull);
|
|
}
|
|
|
|
Future<void> test_lineLength() async {
|
|
const contents = '''
|
|
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, contents);
|
|
|
|
await expectFormattedContents(mainFileUri, contents, expectedDefault);
|
|
await updateConfig({'lineLength': 500});
|
|
await expectFormattedContents(mainFileUri, contents, 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 {
|
|
const contents = '''
|
|
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, contents);
|
|
await expectFormattedContents(mainFileUri, contents, expectedContents);
|
|
}
|
|
|
|
Future<void> test_lineLength_workspaceFolderSpecified() async {
|
|
const contents = '''
|
|
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, contents);
|
|
await expectFormattedContents(mainFileUri, contents, expectedContents);
|
|
}
|
|
|
|
Future<void> test_lineLength_workspaceFolderUnspecified() async {
|
|
const contents = '''
|
|
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, contents);
|
|
await expectFormattedContents(mainFileUri, contents, expectedContents);
|
|
}
|
|
|
|
Future<void> test_minimalEdits_addWhitespace() async {
|
|
// Check we only get one edit to add the required whitespace and not
|
|
// an entire document replacement.
|
|
const contents = '''
|
|
void f(){}
|
|
''';
|
|
const expected = '''
|
|
void f() {}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
var formatEdits = await expectFormattedContents(
|
|
mainFileUri,
|
|
contents,
|
|
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.
|
|
const contents = '''
|
|
|
|
|
|
|
|
void f() {}
|
|
''';
|
|
const expected = '''
|
|
void f() {}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
var formatEdits = await expectFormattedContents(
|
|
mainFileUri,
|
|
contents,
|
|
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.
|
|
const contents = '''
|
|
void f() {}
|
|
|
|
|
|
|
|
|
|
''';
|
|
const expected = '''
|
|
void f() {}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
var formatEdits = await expectFormattedContents(
|
|
mainFileUri,
|
|
contents,
|
|
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.
|
|
const contents = '''
|
|
void f() {}
|
|
''';
|
|
const expected = '''
|
|
void f() {}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
var formatEdits = await expectFormattedContents(
|
|
mainFileUri,
|
|
contents,
|
|
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.
|
|
const contents = '''
|
|
void f()
|
|
|
|
|
|
{}
|
|
''';
|
|
const expected = '''
|
|
void f() {}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
var formatEdits = await expectFormattedContents(
|
|
mainFileUri,
|
|
contents,
|
|
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.
|
|
const contents = '''
|
|
void f( ) { }
|
|
''';
|
|
const expected = '''
|
|
void f() {}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
var formatEdits = await expectFormattedContents(
|
|
mainFileUri,
|
|
contents,
|
|
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).
|
|
const contents = '''
|
|
void f() {
|
|
var a = 1;
|
|
// Comment
|
|
print(a);
|
|
}
|
|
''';
|
|
const expected = '''
|
|
void f() {
|
|
var a = 1;
|
|
// Comment
|
|
print(a);
|
|
}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
var formatEdits = await expectFormattedContents(
|
|
mainFileUri,
|
|
contents,
|
|
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 {
|
|
const contents = '''
|
|
void f ()
|
|
{
|
|
|
|
print('test');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f() {
|
|
print('test');
|
|
}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
await expectFormattedContents(mainFileUri, contents, 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 {
|
|
const optionsContent = '''
|
|
formatter:
|
|
trailing_commas: preserve
|
|
''';
|
|
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_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 {
|
|
const contents = '''
|
|
void f ()
|
|
{
|
|
|
|
print('test');
|
|
}
|
|
''';
|
|
var expected = '''
|
|
void f() {
|
|
print('test');
|
|
}
|
|
''';
|
|
newFile(mainFilePath, contents);
|
|
await initialize();
|
|
await expectFormattedContents(mainFileUri, contents, 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.
|
|
const contents = '''
|
|
void f() {
|
|
print(unresolved);
|
|
}
|
|
''';
|
|
const expected = '''
|
|
void f() {
|
|
print(unresolved);
|
|
}
|
|
''';
|
|
await initialize();
|
|
await openFile(mainFileUri, contents);
|
|
|
|
await expectFormattedContents(mainFileUri, contents, expected);
|
|
}
|
|
}
|