| // Copyright (c) 2014, 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/protocol/protocol_generated.dart'; | |
| import 'package:analysis_server/src/plugin/plugin_isolate.dart'; | |
| import 'package:analysis_server/src/services/correction/fix_internal.dart'; | |
| import 'package:analysis_server/src/session_logger/session_logger.dart'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/instrumentation/service.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_common.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin; | |
| import 'package:analyzer_testing/package_config_file_builder.dart'; | |
| import 'package:linter/src/rules.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(FixesTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class FixesTest extends PubPackageAnalysisServerTest { | |
| @override | |
| Future<void> setUp() async { | |
| super.setUp(); | |
| registerBuiltInFixGenerators(); | |
| await setRoots(included: [workspaceRootPath], excluded: []); | |
| } | |
| Future<void> test_concurrentModifications() async { | |
| var file = server.resourceProvider.getFile(testFile.path); | |
| var futures = <Future<void>>[]; | |
| // Send many requests to modify files and get fixes. | |
| for (var i = 1; i < 100; i++) { | |
| futures.add(_addOverlay(testFile.path, 'var i = $i;')); | |
| await pumpEventQueue(); | |
| futures.add( | |
| handleSuccessfulRequest( | |
| EditGetFixesParams( | |
| file.path, | |
| 0, | |
| ).toRequest('$i', clientUriConverter: server.uriConverter), | |
| ), | |
| ); | |
| await pumpEventQueue(); | |
| } | |
| // Expect all to complete. | |
| await Future.wait(futures); | |
| } | |
| Future<void> test_fileOutsideRoot() async { | |
| var outsideFile = '/foo/test.dart'; | |
| newFile(outsideFile, 'bad code to create error'); | |
| // Set up the original project, as the code fix code won't run at all | |
| // if there are no contexts. | |
| await waitForTasksFinished(); | |
| var request = EditGetFixesParams( | |
| convertPath(outsideFile), | |
| 0, | |
| ).toRequest('0', clientUriConverter: server.uriConverter); | |
| var response = await handleRequest(request); | |
| assertResponseFailure( | |
| response, | |
| requestId: '0', | |
| errorCode: RequestErrorCode.GET_FIXES_INVALID_FILE, | |
| ); | |
| } | |
| Future<void> test_fixUndefinedClass() async { | |
| addTestFile(''' | |
| void f() { | |
| Completer<String> x = null; | |
| print(x); | |
| } | |
| '''); | |
| await waitForTasksFinished(); | |
| var errors = await _getFixesAt(testFile, 'Completer<String>'); | |
| expect(errors, hasLength(1)); | |
| var fixes = errors.first.fixes; | |
| expect(fixes, hasLength(4)); | |
| expect(fixes[0].message, matches("Add 'import' of ")); | |
| expect(fixes[1].message, matches("Add 'import' of .+ with 'show'")); | |
| expect(fixes[2].message, matches('Create class')); | |
| expect(fixes[3].message, matches('Create mixin')); | |
| } | |
| Future<void> test_fromPlugins() async { | |
| var pluginIsolate = PluginIsolate( | |
| 'a', | |
| 'b', | |
| 'c', | |
| TestNotificationManager(), | |
| InstrumentationService.NULL_SERVICE, | |
| SessionLogger(), | |
| isLegacy: true, | |
| ); | |
| var fixes = plugin.AnalysisErrorFixes( | |
| AnalysisError( | |
| AnalysisErrorSeverity.ERROR, | |
| AnalysisErrorType.HINT, | |
| Location('', 0, 0, 0, 0, endLine: 0, endColumn: 0), | |
| 'message', | |
| 'code', | |
| ), | |
| ); | |
| var result = plugin.EditGetFixesResult(<plugin.AnalysisErrorFixes>[fixes]); | |
| pluginManager.broadcastResults = { | |
| pluginIsolate: Future.value(result.toResponse('-', 1)), | |
| }; | |
| addTestFile('void f() {}'); | |
| await waitForTasksFinished(); | |
| var errorFixes = await _getFixesAt(testFile, 'f('); | |
| expect(errorFixes, hasLength(1)); | |
| } | |
| Future<void> test_hasFixes() async { | |
| addTestFile(''' | |
| foo() { | |
| print(1) | |
| } | |
| bar() { | |
| print(10) print(20) | |
| } | |
| '''); | |
| await waitForTasksFinished(); | |
| // print(1) | |
| { | |
| var errorFixes = await _getFixesAt(testFile, 'print(1)'); | |
| expect(errorFixes, hasLength(1)); | |
| _isSyntacticErrorWithMultiFix(errorFixes[0]); | |
| } | |
| // print(10) | |
| { | |
| var errorFixes = await _getFixesAt(testFile, 'print(10)'); | |
| expect(errorFixes, hasLength(2)); | |
| _isSyntacticErrorWithMultiFix(errorFixes[0]); | |
| _isSyntacticErrorWithMultiFix(errorFixes[0]); | |
| } | |
| } | |
| Future<void> test_invalidFilePathFormat_notAbsolute() async { | |
| var request = EditGetFixesParams( | |
| 'test.dart', | |
| 0, | |
| ).toRequest('0', clientUriConverter: server.uriConverter); | |
| var response = await handleRequest(request); | |
| assertResponseFailure( | |
| response, | |
| requestId: '0', | |
| errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT, | |
| ); | |
| } | |
| Future<void> test_invalidFilePathFormat_notNormalized() async { | |
| var request = EditGetFixesParams( | |
| convertPath('/foo/../bar/test.dart'), | |
| 0, | |
| ).toRequest('0', clientUriConverter: server.uriConverter); | |
| var response = await handleRequest(request); | |
| assertResponseFailure( | |
| response, | |
| requestId: '0', | |
| errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT, | |
| ); | |
| } | |
| Future<void> test_overlayOnlyFile() async { | |
| await _addOverlay(testFile.path, ''' | |
| void f() { | |
| print(1) | |
| } | |
| '''); | |
| var file = server.resourceProvider.getFile(testFile.path); | |
| // ask for fixes | |
| await waitForTasksFinished(); | |
| var errorFixes = await _getFixesAt(file, 'print(1)'); | |
| expect(errorFixes, hasLength(1)); | |
| _isSyntacticErrorWithSingleFix(errorFixes[0]); | |
| } | |
| Future<void> test_pubspec_packageNames() async { | |
| registerLintRules(); | |
| newAnalysisOptionsYamlFile(testPackageRootPath, r''' | |
| linter: | |
| rules: | |
| - package_names | |
| '''); | |
| var pubspec = newPubspecYamlFile(testPackageRootPath, r''' | |
| name: fooBar | |
| version: 0.0.1 | |
| '''); | |
| await setRoots(included: [workspaceRootPath], excluded: []); | |
| await waitForTasksFinished(); | |
| expect(await _getFixesAt(pubspec, 'fooBar'), isEmpty); | |
| } | |
| Future<void> test_suggestImportFromDifferentAnalysisRoot() async { | |
| writePackageConfig2( | |
| convertPath('$workspaceRootPath/aaa'), | |
| config: (PackageConfigFileBuilder() | |
| ..add(name: 'bbb', rootFolder: getFolder('$workspaceRootPath/bbb'))), | |
| ); | |
| newPubspecYamlFile('$workspaceRootPath/aaa', r''' | |
| dependencies: | |
| bbb: any | |
| '''); | |
| writePackageConfig2(convertPath('$workspaceRootPath/bbb')); | |
| newFile('$workspaceRootPath/bbb/lib/target.dart', 'class Foo() {}'); | |
| newFile( | |
| '$workspaceRootPath/bbb/lib/target.generated.dart', | |
| 'class Foo() {}', | |
| ); | |
| newFile( | |
| '$workspaceRootPath/bbb/lib/target.template.dart', | |
| 'class Foo() {}', | |
| ); | |
| // Configure the test file. | |
| var file = newFile( | |
| '$workspaceRootPath/aaa/main.dart', | |
| 'void f() { Foo(); }', | |
| ); | |
| await waitForTasksFinished(); | |
| var fixes = (await _getFixesAt( | |
| file, | |
| 'Foo()', | |
| )).single.fixes.map((f) => f.message).toList(); | |
| expect(fixes, contains("Add 'import' of 'package:bbb/target.dart'")); | |
| expect( | |
| fixes, | |
| contains("Add 'import' of 'package:bbb/target.generated.dart'"), | |
| ); | |
| // Context: http://dartbug.com/39401 | |
| expect( | |
| fixes.contains("Add 'import' of 'package:bbb/target.template.dart'"), | |
| isFalse, | |
| ); | |
| } | |
| Future<void> _addOverlay(String name, String contents) async { | |
| await handleSuccessfulRequest( | |
| AnalysisUpdateContentParams({name: AddContentOverlay(contents)}) | |
| .toRequest('0', clientUriConverter: server.uriConverter), | |
| ); | |
| } | |
| Future<List<AnalysisErrorFixes>> _getFixes(File file, int offset) async { | |
| var request = EditGetFixesParams( | |
| file.path, | |
| offset, | |
| ).toRequest('0', clientUriConverter: server.uriConverter); | |
| var response = await handleSuccessfulRequest(request); | |
| var result = EditGetFixesResult.fromResponse( | |
| response, | |
| clientUriConverter: server.uriConverter, | |
| ); | |
| return result.fixes; | |
| } | |
| Future<List<AnalysisErrorFixes>> _getFixesAt(File file, String search) async { | |
| var offset = offsetInFile(file, search); | |
| return await _getFixes(file, offset); | |
| } | |
| void _isSyntacticError(AnalysisErrorFixes fixes) { | |
| var error = fixes.error; | |
| expect(error.severity, AnalysisErrorSeverity.ERROR); | |
| expect(error.type, AnalysisErrorType.SYNTACTIC_ERROR); | |
| } | |
| void _isSyntacticErrorWithMultiFix(AnalysisErrorFixes fixes) { | |
| _isSyntacticError(fixes); | |
| expect(fixes.fixes.length, greaterThan(1)); | |
| } | |
| void _isSyntacticErrorWithSingleFix(AnalysisErrorFixes fixes) { | |
| _isSyntacticError(fixes); | |
| expect(fixes.fixes, hasLength(1)); | |
| } | |
| } |