blob: 8437bc89e20ecc41fde0ade3c5aaba18332f56cb [file] [log] [blame]
// 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:io';
import 'package:dart_services/src/analysis_server.dart';
import 'package:dart_services/src/common.dart';
import 'package:dart_services/src/protos/dart_services.pb.dart' as proto;
import 'package:dart_services/src/sdk.dart';
import 'package:test/test.dart';
const completionCode = r'''
void main() {
int i = 0;
i.
}
''';
const completionFilterCode = r'''
void main() {
pr
}
''';
const completionLargeNamespaces = r'''
class A {}
class AB {}
class ABC {}
void main() {
var c = A
}
class ZZ {}
class a {}
''';
const quickFixesCode = r'''
void main() {
int i = 0
}
''';
const badFormatCode = r'''
void main()
{
int i = 0;
}
''';
const formattedCode = r'''
void main() {
int i = 0;
}
''';
const formatWithIssues = '''
void main() { foo() }
''';
const lintWarningTrigger = '''
void main() async {
var unknown;
print(unknown);
}
''';
final channel = Platform.environment['FLUTTER_CHANNEL'] ?? stableChannel;
void main() => defineTests();
void defineTests() {
late AnalysisServerWrapper analysisServer;
/// Returns whether the completion [response] contains [expected].
bool completionsContains(proto.CompleteResponse response, String expected) =>
response.completions
.any((completion) => completion.completion['completion'] == expected);
group('Platform SDK analysis_server', () {
late Sdk sdk;
setUp(() async {
sdk = Sdk.create(channel);
analysisServer = DartAnalysisServerWrapper(dartSdkPath: sdk.dartSdkPath);
await analysisServer.init();
});
tearDown(() => analysisServer.shutdown());
test('simple_completion', () async {
// Just after `i.` on line 3 of [completionCode]
final results = await analysisServer.complete(completionCode, 32);
expect(results.replacementLength, 0);
expect(results.replacementOffset, 32);
final completions =
results.completions.map((c) => c.completion['completion']).toList();
expect(completions, contains('abs'));
expect(completionsContains(results, 'codeUnitAt'), false);
});
// https://github.com/dart-lang/dart-pad/issues/2005
test('Trigger lint with Dart code', () async {
final results = await analysisServer.analyze(lintWarningTrigger);
expect(results.issues.length, 1);
final issue = results.issues[0];
expect(issue.line, 2);
expect(issue.column, 7);
expect(issue.kind, 'info');
expect(issue.message,
'An uninitialized variable should have an explicit type annotation.');
expect(issue.code, 'prefer_typing_uninitialized_variables');
});
test('repro #126 - completions polluted on second request', () async {
// https://github.com/dart-lang/dart-services/issues/126
return analysisServer.complete(completionFilterCode, 17).then((results) {
return analysisServer
.complete(completionFilterCode, 17)
.then((results) {
expect(results.replacementLength, 2);
expect(results.replacementOffset, 16);
expect(completionsContains(results, 'print'), true);
expect(completionsContains(results, 'pow'), false);
});
});
});
test('import_test', () async {
// We're testing here that we don't have any path imports - we don't want
// to enable browsing the file system.
final testCode = "import '/'; main() { int a = 0; a. }";
final results = await analysisServer.complete(testCode, 9);
final completions = results.completions;
if (completions.isNotEmpty) {
expect(completions.every((completion) {
return completion.completion['completion']!.startsWith('dart:');
}), true);
}
});
test('import_dart_core_test', () async {
// Ensure we can import dart: imports.
final testCode = "import 'dart:c'; main() { int a = 0; a. }";
final results = await analysisServer.complete(testCode, 14);
final completions = results.completions;
expect(
completions.every((completion) =>
completion.completion['completion']!.startsWith('dart:')),
true,
);
expect(
completions.any((completion) =>
completion.completion['completion']!.startsWith('dart:')),
true,
);
});
test('import_and_other_test', () async {
final testCode = "import '/'; main() { int a = 0; a. }";
final results = await analysisServer.complete(testCode, 34);
expect(completionsContains(results, 'abs'), true);
});
test('simple_quickFix', () async {
final results = await analysisServer.getFixes(quickFixesCode, 25);
expect(results.fixes.length, 2);
// Fixes are not guaranteed to arrive in a particular order.
results.fixes.sort((a, b) => a.offset.compareTo(b.offset));
expect(results.fixes[0].offset, 20);
expect(results.fixes[0].length, 1); // We need an insertion.
expect(results.fixes[1].offset, 24);
expect(results.fixes[1].length, 1); // We need an insertion.
expect(results.fixes[1].fixes.length, 1);
final candidateFix = results.fixes[1].fixes[0];
expect(candidateFix.message.contains(';'), true);
expect(candidateFix.edits[0].length, 0);
expect(candidateFix.edits[0].offset, 25);
expect(candidateFix.edits[0].replacement, ';');
});
test('simple_format', () async {
final results = await analysisServer.format(badFormatCode, 0);
expect(results.newString, formattedCode);
});
test('format good code', () async {
final results =
await analysisServer.format(formattedCode.replaceAll('\n', ' '), 0);
expect(results.newString, formattedCode);
});
test('format with issues', () async {
final results = await analysisServer.format(formatWithIssues, 0);
expect(results.newString, formatWithIssues);
});
test('analyze', () async {
final results = await analysisServer.analyze(sampleCode);
expect(results.issues, isEmpty);
});
test('analyze with errors', () async {
final results = await analysisServer.analyze(sampleCodeError);
expect(results.issues, hasLength(1));
});
test('filter completions', () async {
// just after A
final idx = 61;
expect(completionLargeNamespaces.substring(idx - 1, idx), 'A');
final results =
await analysisServer.complete(completionLargeNamespaces, 61);
expect(completionsContains(results, 'A'), true);
expect(completionsContains(results, 'AB'), true);
expect(completionsContains(results, 'ABC'), true);
expect(completionsContains(results, 'a'), true);
expect(completionsContains(results, 'ZZ'), false);
});
});
group('Flutter cached SDK analysis_server', () {
setUp(() async {
final sdk = Sdk.create(channel);
analysisServer =
FlutterAnalysisServerWrapper(dartSdkPath: sdk.dartSdkPath);
await analysisServer.init();
});
tearDown(() => analysisServer.shutdown());
test('analyze working Dart code', () async {
final results = await analysisServer.analyze(sampleCode);
expect(results.issues, isEmpty);
});
test('analyze working Flutter code', () async {
final results = await analysisServer.analyze(sampleCode);
expect(results.issues, isEmpty);
});
});
//--------------------------------------------------------
// Begin testing the multi file group files={} map entry points:
group('Platform SDK analysis_server multifile files={}', () {
late Sdk sdk;
setUp(() async {
sdk = Sdk.create(channel);
analysisServer = DartAnalysisServerWrapper(dartSdkPath: sdk.dartSdkPath);
await analysisServer.init();
});
tearDown(() => analysisServer.shutdown());
// Now test multi file 'files:{}' source format.
const kMainDart = 'main.dart';
test('files={} simple_completion', () async {
// Just after `i.` on line 3 of [completionCode]
final results = await analysisServer
.completeFiles({kMainDart: completionCode}, Location(kMainDart, 32));
expect(results.replacementLength, 0);
expect(results.replacementOffset, 32);
final completions =
results.completions.map((c) => c.completion['completion']).toList();
expect(completions, contains('abs'));
expect(completionsContains(results, 'codeUnitAt'), false);
});
// https://github.com/dart-lang/dart-pad/issues/2005
test('files={} Trigger lint with Dart code', () async {
final results =
await analysisServer.analyzeFiles({kMainDart: lintWarningTrigger});
expect(results.issues.length, 1);
final issue = results.issues[0];
expect(issue.line, 2);
expect(issue.kind, 'info');
expect(issue.message,
'An uninitialized variable should have an explicit type annotation.');
});
test('files={} repro #126 - completions polluted on second request',
() async {
final files = <String, String>{kMainDart: completionFilterCode};
final location = Location(kMainDart, 17);
// https://github.com/dart-lang/dart-services/issues/126
return analysisServer.completeFiles(files, location).then((results) {
return analysisServer.completeFiles(files, location).then((results) {
expect(results.replacementLength, 2);
expect(results.replacementOffset, 16);
expect(completionsContains(results, 'print'), true);
expect(completionsContains(results, 'pow'), false);
});
});
});
test('files={} import_test', () async {
// We're testing here that we don't have any path imports - we don't want
// to enable browsing the file system.
final testCode = "import '/'; main() { int a = 0; a. }";
final results = await analysisServer
.completeFiles({kMainDart: testCode}, Location(kMainDart, 9));
final completions = results.completions;
if (completions.isNotEmpty) {
expect(completions.every((completion) {
return completion.completion['completion']!.startsWith('dart:');
}), true);
}
});
test('files={} import_dart_core_test', () async {
// Ensure we can import dart: imports.
final testCode = "import 'dart:c'; main() { int a = 0; a. }";
final results = await analysisServer
.completeFiles({kMainDart: testCode}, Location(kMainDart, 14));
final completions = results.completions;
expect(
completions.every((completion) =>
completion.completion['completion']!.startsWith('dart:')),
true,
);
expect(
completions.any((completion) =>
completion.completion['completion']!.startsWith('dart:')),
true,
);
});
test('files={} import_and_other_test', () async {
final testCode = "import '/'; main() { int a = 0; a. }";
final results = await analysisServer
.completeFiles({kMainDart: testCode}, Location(kMainDart, 34));
expect(completionsContains(results, 'abs'), true);
});
test('files={} myRandomName.dart + simple_quickFix', () async {
final results = await analysisServer.getFixesMulti(
{'myRandomName.dart': quickFixesCode},
Location('myRandomName.dart', 25));
expect(results.fixes.length, 2);
// Fixes are not guaranteed to arrive in a particular order.
results.fixes.sort((a, b) => a.offset.compareTo(b.offset));
expect(results.fixes[0].offset, 20);
expect(results.fixes[0].length, 1); // We need an insertion.
expect(results.fixes[1].offset, 24);
expect(results.fixes[1].length, 1); // We need an insertion.
expect(results.fixes[1].fixes.length, 1);
final candidateFix = results.fixes[1].fixes[0];
expect(candidateFix.message.contains(';'), true);
expect(candidateFix.edits[0].length, 0);
expect(candidateFix.edits[0].offset, 25);
expect(candidateFix.edits[0].replacement, ';');
});
test('files={} analyze', () async {
final results =
await analysisServer.analyzeFiles({kMainDart: sampleCode});
expect(results.issues, isEmpty);
});
test('files={} analyze with errors', () async {
final results =
await analysisServer.analyzeFiles({kMainDart: sampleCodeError});
expect(results.issues, hasLength(1));
});
test('files={} filter completions', () async {
// just after A
final idx = 61;
expect(completionLargeNamespaces.substring(idx - 1, idx), 'A');
final results = await analysisServer.completeFiles(
{kMainDart: completionLargeNamespaces}, Location(kMainDart, 61));
expect(completionsContains(results, 'A'), true);
expect(completionsContains(results, 'AB'), true);
expect(completionsContains(results, 'ABC'), true);
expect(completionsContains(results, 'a'), true);
expect(completionsContains(results, 'ZZ'), false);
});
});
}