| // Copyright (c) 2016, 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. |
| |
| library analyzer.test.driver; |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/file_system/memory_file_system.dart'; |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/analysis/file_state.dart'; |
| import 'package:analyzer/src/dart/analysis/status.dart'; |
| import 'package:analyzer/src/dart/analysis/top_level_declaration.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; |
| import 'package:analyzer/src/generated/sdk.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/summary/idl.dart'; |
| import 'package:convert/convert.dart'; |
| import 'package:crypto/crypto.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../../context/mock_sdk.dart'; |
| import 'base.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(AnalysisDriverTest); |
| defineReflectiveTests(AnalysisDriverSchedulerTest); |
| }); |
| } |
| |
| /** |
| * Returns a [Future] that completes after pumping the event queue [times] |
| * times. By default, this should pump the event queue enough times to allow |
| * any code to run, as long as it's not waiting on some external event. |
| */ |
| Future pumpEventQueue([int times = 5000]) { |
| if (times == 0) return new Future.value(); |
| // We use a delayed future to allow microtask events to finish. The |
| // Future.value or Future() constructors use scheduleMicrotask themselves and |
| // would therefore not wait for microtask callbacks that are scheduled after |
| // invoking this method. |
| return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); |
| } |
| |
| @reflectiveTest |
| class AnalysisDriverSchedulerTest { |
| final MemoryResourceProvider provider = new MemoryResourceProvider(); |
| DartSdk sdk; |
| final ByteStore byteStore = new MemoryByteStore(); |
| final FileContentOverlay contentOverlay = new FileContentOverlay(); |
| |
| final StringBuffer logBuffer = new StringBuffer(); |
| PerformanceLog logger; |
| |
| AnalysisDriverScheduler scheduler; |
| |
| List<AnalysisResult> allResults = []; |
| |
| AnalysisDriver newDriver() { |
| sdk = new MockSdk(resourceProvider: provider); |
| AnalysisDriver driver = new AnalysisDriver( |
| scheduler, |
| logger, |
| provider, |
| byteStore, |
| contentOverlay, |
| 'test', |
| new SourceFactory( |
| [new DartUriResolver(sdk), new ResourceUriResolver(provider)], |
| null, |
| provider), |
| new AnalysisOptionsImpl()..strongMode = true); |
| driver.results.forEach(allResults.add); |
| return driver; |
| } |
| |
| void setUp() { |
| sdk = new MockSdk(resourceProvider: provider); |
| logger = new PerformanceLog(logBuffer); |
| scheduler = new AnalysisDriverScheduler(logger); |
| scheduler.start(); |
| } |
| |
| test_priorities_getResult_beforePriority() async { |
| AnalysisDriver driver1 = newDriver(); |
| AnalysisDriver driver2 = newDriver(); |
| |
| String a = _p('/a.dart'); |
| String b = _p('/b.dart'); |
| String c = _p('/c.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile(c, 'class C {}'); |
| driver1.addFile(a); |
| driver2.addFile(b); |
| driver2.addFile(c); |
| driver1.priorityFiles = [a]; |
| driver2.priorityFiles = [a]; |
| |
| AnalysisResult result = await driver2.getResult(b); |
| expect(result.path, b); |
| |
| await driver1.status.firstWhere((status) => status.isIdle); |
| await driver2.status.firstWhere((status) => status.isIdle); |
| |
| expect(allResults, hasLength(3)); |
| expect(allResults[0].path, b); |
| expect(allResults[1].path, a); |
| expect(allResults[2].path, c); |
| } |
| |
| test_priorities_priorityBeforeGeneral1() async { |
| AnalysisDriver driver1 = newDriver(); |
| AnalysisDriver driver2 = newDriver(); |
| |
| String a = _p('/a.dart'); |
| String b = _p('/b.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| driver1.addFile(a); |
| driver2.addFile(b); |
| driver1.priorityFiles = [a]; |
| driver2.priorityFiles = [a]; |
| |
| await driver1.status.firstWhere((status) => status.isIdle); |
| await driver2.status.firstWhere((status) => status.isIdle); |
| |
| expect(allResults, hasLength(2)); |
| expect(allResults[0].path, a); |
| expect(allResults[1].path, b); |
| } |
| |
| test_priorities_priorityBeforeGeneral2() async { |
| AnalysisDriver driver1 = newDriver(); |
| AnalysisDriver driver2 = newDriver(); |
| |
| String a = _p('/a.dart'); |
| String b = _p('/b.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| driver1.addFile(a); |
| driver2.addFile(b); |
| driver1.priorityFiles = [b]; |
| driver2.priorityFiles = [b]; |
| |
| await driver1.status.firstWhere((status) => status.isIdle); |
| await driver2.status.firstWhere((status) => status.isIdle); |
| |
| expect(allResults, hasLength(2)); |
| expect(allResults[0].path, b); |
| expect(allResults[1].path, a); |
| } |
| |
| test_priorities_priorityBeforeGeneral3() async { |
| AnalysisDriver driver1 = newDriver(); |
| AnalysisDriver driver2 = newDriver(); |
| |
| String a = _p('/a.dart'); |
| String b = _p('/b.dart'); |
| String c = _p('/c.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile(c, 'class C {}'); |
| driver1.addFile(a); |
| driver1.addFile(b); |
| driver2.addFile(c); |
| driver1.priorityFiles = [a, c]; |
| driver2.priorityFiles = [a, c]; |
| |
| await driver1.status.firstWhere((status) => status.isIdle); |
| await driver2.status.firstWhere((status) => status.isIdle); |
| |
| expect(allResults, hasLength(3)); |
| expect(allResults[0].path, a); |
| expect(allResults[1].path, c); |
| expect(allResults[2].path, b); |
| } |
| |
| test_status() async { |
| AnalysisDriver driver1 = newDriver(); |
| AnalysisDriver driver2 = newDriver(); |
| |
| String a = _p('/a.dart'); |
| String b = _p('/b.dart'); |
| String c = _p('/c.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile(c, 'class C {}'); |
| driver1.addFile(a); |
| driver2.addFile(b); |
| driver2.addFile(c); |
| |
| Monitor idleStatusMonitor = new Monitor(); |
| List<AnalysisStatus> allStatuses = []; |
| scheduler.status.forEach((status) { |
| allStatuses.add(status); |
| if (status.isIdle) { |
| idleStatusMonitor.notify(); |
| } |
| }); |
| |
| await idleStatusMonitor.signal; |
| |
| expect(allStatuses, hasLength(2)); |
| expect(allStatuses[0].isAnalyzing, isTrue); |
| expect(allStatuses[1].isAnalyzing, isFalse); |
| |
| expect(allResults, hasLength(3)); |
| } |
| |
| test_status_analyzingOnlyWhenHasFilesToAnalyze() async { |
| AnalysisDriver driver1 = newDriver(); |
| AnalysisDriver driver2 = newDriver(); |
| |
| String a = _p('/a.dart'); |
| String b = _p('/b.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| driver1.addFile(a); |
| driver2.addFile(b); |
| |
| Monitor idleStatusMonitor = new Monitor(); |
| List<AnalysisStatus> allStatuses = []; |
| scheduler.status.forEach((status) { |
| allStatuses.add(status); |
| if (status.isIdle) { |
| idleStatusMonitor.notify(); |
| } |
| }); |
| |
| // The two added files were analyzed, and the schedule is idle. |
| await idleStatusMonitor.signal; |
| expect(allStatuses, hasLength(2)); |
| expect(allStatuses[0].isAnalyzing, isTrue); |
| expect(allStatuses[1].isAnalyzing, isFalse); |
| allStatuses.clear(); |
| |
| // We don't transition to analysis and back to idle. |
| await driver1.getFilesReferencingName('X'); |
| expect(allStatuses, isEmpty); |
| } |
| |
| String _p(String path) => provider.convertPath(path); |
| } |
| |
| @reflectiveTest |
| class AnalysisDriverTest extends BaseAnalysisDriverTest { |
| test_addedFiles() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| |
| driver.addFile(a); |
| expect(driver.addedFiles, contains(a)); |
| expect(driver.addedFiles, isNot(contains(b))); |
| |
| driver.removeFile(a); |
| expect(driver.addedFiles, isNot(contains(a))); |
| expect(driver.addedFiles, isNot(contains(b))); |
| } |
| |
| test_addFile_shouldRefresh() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| |
| provider.newFile(a, 'class A {}'); |
| provider.newFile( |
| b, |
| r''' |
| import 'a.dart'; |
| '''); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| |
| void assertNumberOfErrorsInB(int n) { |
| var bResult = allResults.singleWhere((r) => r.path == b); |
| expect(bResult.errors, hasLength(n)); |
| allResults.clear(); |
| } |
| |
| // Initial analysis, 'b' does not use 'a', so there is a hint. |
| await driver.waitForIdle(); |
| assertNumberOfErrorsInB(1); |
| |
| // Update 'b' to use 'a', no more hints. |
| provider.newFile( |
| b, |
| r''' |
| import 'a.dart'; |
| main() { |
| print(A); |
| } |
| '''); |
| driver.changeFile(b); |
| await driver.waitForIdle(); |
| assertNumberOfErrorsInB(0); |
| |
| // Change 'b' content so that it has a hint. |
| // Remove 'b' and add it again. |
| // The file 'b' must be refreshed, and the hint must be reported. |
| provider.newFile( |
| b, |
| r''' |
| import 'a.dart'; |
| '''); |
| driver.removeFile(b); |
| driver.addFile(b); |
| await driver.waitForIdle(); |
| assertNumberOfErrorsInB(1); |
| } |
| |
| test_addFile_thenRemove() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| driver.addFile(a); |
| driver.addFile(b); |
| |
| // Now remove 'a'. |
| driver.removeFile(a); |
| |
| await driver.waitForIdle(); |
| |
| // Only 'b' has been analyzed, because 'a' was removed before we started. |
| expect(allResults, hasLength(1)); |
| expect(allResults[0].path, b); |
| } |
| |
| test_cachedPriorityResults() async { |
| var a = _p('/test/bin/a.dart'); |
| provider.newFile(a, 'var a = 1;'); |
| |
| driver.priorityFiles = [a]; |
| |
| AnalysisResult result1 = await driver.getResult(a); |
| expect(driver.test.priorityResults, containsPair(a, result1)); |
| |
| AnalysisResult result2 = await driver.getResult(a); |
| expect(result2, same(result1)); |
| } |
| |
| test_cachedPriorityResults_flush_onAnyFileChange() async { |
| var a = _p('/test/bin/a.dart'); |
| var b = _p('/test/bin/b.dart'); |
| provider.newFile(a, 'var a = 1;'); |
| provider.newFile(a, 'var b = 2;'); |
| |
| driver.priorityFiles = [a]; |
| |
| AnalysisResult result1 = await driver.getResult(a); |
| expect(driver.test.priorityResults, containsPair(a, result1)); |
| |
| // Change a file. |
| // The cache is flushed. |
| driver.changeFile(a); |
| expect(driver.test.priorityResults, isEmpty); |
| AnalysisResult result2 = await driver.getResult(a); |
| expect(driver.test.priorityResults, containsPair(a, result2)); |
| |
| // Add a file. |
| // The cache is flushed. |
| driver.addFile(b); |
| expect(driver.test.priorityResults, isEmpty); |
| AnalysisResult result3 = await driver.getResult(a); |
| expect(driver.test.priorityResults, containsPair(a, result3)); |
| |
| // Remove a file. |
| // The cache is flushed. |
| driver.removeFile(b); |
| expect(driver.test.priorityResults, isEmpty); |
| } |
| |
| test_cachedPriorityResults_flush_onPrioritySetChange() async { |
| var a = _p('/test/bin/a.dart'); |
| var b = _p('/test/bin/b.dart'); |
| provider.newFile(a, 'var a = 1;'); |
| provider.newFile(a, 'var b = 2;'); |
| |
| driver.priorityFiles = [a]; |
| |
| AnalysisResult result1 = await driver.getResult(a); |
| expect(driver.test.priorityResults, hasLength(1)); |
| expect(driver.test.priorityResults, containsPair(a, result1)); |
| |
| // Make "a" and "b" priority. |
| // We still have the result for "a" cached. |
| driver.priorityFiles = [a, b]; |
| expect(driver.test.priorityResults, hasLength(1)); |
| expect(driver.test.priorityResults, containsPair(a, result1)); |
| |
| // Get the result for "b". |
| AnalysisResult result2 = await driver.getResult(b); |
| expect(driver.test.priorityResults, hasLength(2)); |
| expect(driver.test.priorityResults, containsPair(a, result1)); |
| expect(driver.test.priorityResults, containsPair(b, result2)); |
| |
| // Only "b" is priority. |
| // The result for "a" is flushed. |
| driver.priorityFiles = [b]; |
| expect(driver.test.priorityResults, hasLength(1)); |
| expect(driver.test.priorityResults, containsPair(b, result2)); |
| } |
| |
| test_cachedPriorityResults_notPriority() async { |
| var a = _p('/test/bin/a.dart'); |
| provider.newFile(a, 'var a = 1;'); |
| |
| AnalysisResult result1 = await driver.getResult(a); |
| expect(driver.test.priorityResults, isEmpty); |
| |
| // The file is not priority, so its result is not cached. |
| AnalysisResult result2 = await driver.getResult(a); |
| expect(result2, isNot(same(result1))); |
| } |
| |
| test_changeFile_implicitlyAnalyzed() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| provider.newFile( |
| a, |
| r''' |
| import 'b.dart'; |
| var A = B; |
| '''); |
| provider.newFile(b, 'var B = 1;'); |
| |
| driver.priorityFiles = [a]; |
| driver.addFile(a); |
| |
| // We have a result only for "a". |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| { |
| AnalysisResult ar = allResults.firstWhere((r) => r.path == a); |
| expect(_getTopLevelVarType(ar.unit, 'A'), 'int'); |
| } |
| allResults.clear(); |
| |
| // Change "b" and notify. |
| provider.updateFile(b, 'var B = 1.2;'); |
| driver.changeFile(b); |
| |
| // "b" is not an added file, so it is not scheduled for analysis. |
| expect(driver.test.filesToAnalyze, isEmpty); |
| |
| // While "b" is not analyzed explicitly, it is analyzed implicitly. |
| // The change causes "a" to be reanalyzed. |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| { |
| AnalysisResult ar = allResults.firstWhere((r) => r.path == a); |
| expect(_getTopLevelVarType(ar.unit, 'A'), 'double'); |
| } |
| } |
| |
| test_changeFile_notUsed() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/other/b.dart'); |
| provider.newFile(a, ''); |
| provider.newFile(b, 'class B1 {}'); |
| |
| driver.addFile(a); |
| |
| await driver.waitForIdle(); |
| allResults.clear(); |
| |
| // Change "b" and notify. |
| // Nothing depends on "b", so nothing is analyzed. |
| provider.updateFile(b, 'class B2 {}'); |
| driver.changeFile(b); |
| await driver.waitForIdle(); |
| expect(allResults, isEmpty); |
| |
| // This should not add "b" to the file state. |
| expect(driver.fsState.knownFilePaths, isNot(contains(b))); |
| } |
| |
| test_changeFile_selfConsistent() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| provider.newFile( |
| a, |
| r''' |
| import 'b.dart'; |
| var A1 = 1; |
| var A2 = B1; |
| '''); |
| provider.newFile( |
| b, |
| r''' |
| import 'a.dart'; |
| var B1 = A1; |
| '''); |
| |
| driver.priorityFiles = [a, b]; |
| driver.addFile(a); |
| driver.addFile(b); |
| await driver.waitForIdle(); |
| |
| // We have results for both "a" and "b". |
| expect(allResults, hasLength(2)); |
| { |
| AnalysisResult ar = allResults.firstWhere((r) => r.path == a); |
| expect(_getTopLevelVarType(ar.unit, 'A1'), 'int'); |
| expect(_getTopLevelVarType(ar.unit, 'A2'), 'int'); |
| } |
| { |
| AnalysisResult br = allResults.firstWhere((r) => r.path == b); |
| expect(_getTopLevelVarType(br.unit, 'B1'), 'int'); |
| } |
| |
| // Clear the results and update "a". |
| allResults.clear(); |
| provider.updateFile( |
| a, |
| r''' |
| import 'b.dart'; |
| var A1 = 1.2; |
| var A2 = B1; |
| '''); |
| driver.changeFile(a); |
| |
| // We again get results for both "a" and "b". |
| // The results are consistent. |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(2)); |
| { |
| AnalysisResult ar = allResults.firstWhere((r) => r.path == a); |
| expect(_getTopLevelVarType(ar.unit, 'A1'), 'double'); |
| expect(_getTopLevelVarType(ar.unit, 'A2'), 'double'); |
| } |
| { |
| AnalysisResult br = allResults.firstWhere((r) => r.path == b); |
| expect(_getTopLevelVarType(br.unit, 'B1'), 'double'); |
| } |
| } |
| |
| test_changeFile_single() async { |
| addTestFile('var V = 1;', priority: true); |
| |
| // Initial analysis. |
| { |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| AnalysisResult result = allResults[0]; |
| expect(result.path, testFile); |
| expect(_getTopLevelVarType(result.unit, 'V'), 'int'); |
| } |
| |
| // Update the file, but don't notify the driver. |
| allResults.clear(); |
| provider.updateFile(testFile, 'var V = 1.2'); |
| |
| // No new results. |
| await pumpEventQueue(); |
| expect(allResults, isEmpty); |
| |
| // Notify the driver about the change. |
| driver.changeFile(testFile); |
| |
| // The file was added, so it is scheduled for analysis. |
| expect(driver.test.filesToAnalyze, contains(testFile)); |
| |
| // We get a new result. |
| { |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| AnalysisResult result = allResults[0]; |
| expect(result.path, testFile); |
| expect(_getTopLevelVarType(result.unit, 'V'), 'double'); |
| } |
| } |
| |
| test_errors_uriDoesNotExist_export() async { |
| addTestFile(r''' |
| export 'foo.dart'; |
| '''); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| List<AnalysisError> errors = result.errors; |
| expect(errors, hasLength(1)); |
| expect(errors[0].errorCode, CompileTimeErrorCode.URI_DOES_NOT_EXIST); |
| } |
| |
| test_errors_uriDoesNotExist_import() async { |
| addTestFile(r''' |
| import 'foo.dart'; |
| '''); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| List<AnalysisError> errors = result.errors; |
| expect(errors, hasLength(1)); |
| expect(errors[0].errorCode, CompileTimeErrorCode.URI_DOES_NOT_EXIST); |
| } |
| |
| test_errors_uriDoesNotExist_import_deferred() async { |
| addTestFile( |
| r''' |
| import 'foo.dart' deferred as foo; |
| main() { |
| foo.loadLibrary(); |
| } |
| ''', |
| priority: true); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| List<AnalysisError> errors = result.errors; |
| expect(errors, hasLength(1)); |
| expect(errors[0].errorCode, CompileTimeErrorCode.URI_DOES_NOT_EXIST); |
| } |
| |
| test_errors_uriDoesNotExist_part() async { |
| addTestFile(r''' |
| library lib; |
| part 'foo.dart'; |
| '''); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| List<AnalysisError> errors = result.errors; |
| expect(errors, hasLength(1)); |
| expect(errors[0].errorCode, CompileTimeErrorCode.URI_DOES_NOT_EXIST); |
| } |
| |
| test_getFilesReferencingName() async { |
| var a = _p('/test/bin/a.dart'); |
| var b = _p('/test/bin/b.dart'); |
| var c = _p('/test/bin/c.dart'); |
| var d = _p('/test/bin/d.dart'); |
| var e = _p('/test/bin/e.dart'); |
| |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, "import 'a.dart'; A a;"); |
| provider.newFile(c, "import 'a.dart'; var a = new A();"); |
| provider.newFile(d, "class A{} A a;"); |
| provider.newFile(e, "import 'a.dart'; main() {}"); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| driver.addFile(d); |
| driver.addFile(e); |
| |
| // 'b.dart' references an external 'A'. |
| // 'c.dart' references an external 'A'. |
| // 'd.dart' references the local 'A'. |
| // 'e.dart' does not reference 'A' at all. |
| List<String> files = await driver.getFilesReferencingName('A'); |
| expect(files, unorderedEquals([b, c])); |
| |
| // We get the same results second time. |
| List<String> files2 = await driver.getFilesReferencingName('A'); |
| expect(files2, unorderedEquals([b, c])); |
| } |
| |
| test_getIndex() async { |
| String content = r''' |
| foo(int p) {} |
| main() { |
| foo(42); |
| } |
| '''; |
| addTestFile(content); |
| |
| AnalysisDriverUnitIndex index = await driver.getIndex(testFile); |
| |
| int unitId = index.strings.indexOf('package:test/test.dart'); |
| int fooId = index.strings.indexOf('foo'); |
| expect(unitId, isNonNegative); |
| expect(fooId, isNonNegative); |
| } |
| |
| test_getResult() async { |
| String content = 'int f() => 42;'; |
| addTestFile(content, priority: true); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| expect(result.path, testFile); |
| expect(result.uri.toString(), 'package:test/test.dart'); |
| expect(result.exists, isTrue); |
| expect(result.content, content); |
| expect(result.contentHash, _md5(content)); |
| expect(result.unit, isNotNull); |
| expect(result.errors, hasLength(0)); |
| |
| var f = result.unit.declarations[0] as FunctionDeclaration; |
| expect(f.name.staticType.toString(), '() → int'); |
| expect(f.returnType.type.toString(), 'int'); |
| |
| // The same result is also received through the stream. |
| await driver.waitForIdle(); |
| expect(allResults, [result]); |
| } |
| |
| test_getResult_constants_defaultParameterValue_localFunction() async { |
| var a = _p('/test/bin/a.dart'); |
| var b = _p('/test/bin/b.dart'); |
| provider.newFile(a, 'const C = 42;'); |
| provider.newFile( |
| b, |
| r''' |
| import 'a.dart'; |
| main() { |
| foo({int p: C}) {} |
| foo(); |
| } |
| '''); |
| driver.addFile(a); |
| driver.addFile(b); |
| await driver.waitForIdle(); |
| |
| AnalysisResult result = await driver.getResult(b); |
| expect(result.errors, isEmpty); |
| } |
| |
| test_getResult_doesNotExist() async { |
| var a = _p('/test/lib/a.dart'); |
| |
| AnalysisResult result = await driver.getResult(a); |
| expect(result.path, a); |
| expect(result.uri.toString(), 'package:test/a.dart'); |
| expect(result.exists, isFalse); |
| expect(result.content, ''); |
| } |
| |
| test_getResult_errors() async { |
| String content = 'main() { int vv; }'; |
| addTestFile(content, priority: true); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| expect(result.path, testFile); |
| expect(result.errors, hasLength(1)); |
| { |
| AnalysisError error = result.errors[0]; |
| expect(error.offset, 13); |
| expect(error.length, 2); |
| expect(error.errorCode, HintCode.UNUSED_LOCAL_VARIABLE); |
| expect(error.message, "The value of the local variable 'vv' isn't used."); |
| expect(error.correction, "Try removing the variable, or using it."); |
| } |
| } |
| |
| test_getResult_fileContentOverlay_throughAnalysisContext() async { |
| var a = _p('/test/bin/a.dart'); |
| var b = _p('/test/bin/b.dart'); |
| |
| provider.newFile(a, 'import "b.dart";'); |
| provider.newFile(b, 'var v = 1;'); |
| contentOverlay[b] = 'var v = 2;'; |
| |
| var result = await driver.getResult(a); |
| |
| // The content that was set into the overlay for "b" should be visible |
| // through the AnalysisContext that was used to analyze "a". |
| CompilationUnitElement unitA = result.unit.element; |
| Source sourceB = unitA.library.imports[0].importedLibrary.source; |
| expect(unitA.context.getContents(sourceB).data, 'var v = 2;'); |
| } |
| |
| test_getResult_inferTypes_finalField() async { |
| addTestFile( |
| r''' |
| class C { |
| final f = 42; |
| } |
| ''', |
| priority: true); |
| await driver.waitForIdle(); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| expect(_getClassFieldType(result.unit, 'C', 'f'), 'int'); |
| } |
| |
| test_getResult_inferTypes_instanceMethod() async { |
| addTestFile( |
| r''' |
| class A { |
| int m(double p) => 1; |
| } |
| class B extends A { |
| m(double p) => 2; |
| } |
| ''', |
| priority: true); |
| await driver.waitForIdle(); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| expect(_getClassMethodReturnType(result.unit, 'A', 'm'), 'int'); |
| expect(_getClassMethodReturnType(result.unit, 'B', 'm'), 'int'); |
| } |
| |
| test_getResult_invalid_annotation_functionAsConstructor() async { |
| addTestFile( |
| r''' |
| fff() {} |
| |
| @fff() |
| class C {} |
| ''', |
| priority: true); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| ClassDeclaration c = result.unit.declarations[1] as ClassDeclaration; |
| Annotation a = c.metadata[0]; |
| expect(a.name.name, 'fff'); |
| expect(a.name.staticElement, new isInstanceOf<FunctionElement>()); |
| } |
| |
| test_getResult_invalidUri_exports_dart() async { |
| String content = r''' |
| export 'dart:async'; |
| export 'dart:noSuchLib'; |
| export 'dart:math'; |
| '''; |
| addTestFile(content, priority: true); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| expect(result.path, testFile); |
| // Has only exports for valid URIs. |
| List<ExportElement> imports = resolutionMap |
| .elementDeclaredByCompilationUnit(result.unit) |
| .library |
| .exports; |
| expect( |
| imports.map((import) => import.exportedLibrary.source.uri.toString()), |
| unorderedEquals(['dart:async', 'dart:math'])); |
| } |
| |
| test_getResult_invalidUri_imports_dart() async { |
| String content = r''' |
| import 'dart:async'; |
| import 'dart:noSuchLib'; |
| import 'dart:math'; |
| '''; |
| addTestFile(content, priority: true); |
| |
| AnalysisResult result = await driver.getResult(testFile); |
| expect(result.path, testFile); |
| // Has only imports for valid URIs. |
| List<ImportElement> imports = resolutionMap |
| .elementDeclaredByCompilationUnit(result.unit) |
| .library |
| .imports; |
| expect( |
| imports.map((import) => import.importedLibrary.source.uri.toString()), |
| unorderedEquals(['dart:async', 'dart:math', 'dart:core'])); |
| } |
| |
| test_getResult_mix_fileAndPackageUris() async { |
| var a = _p('/test/bin/a.dart'); |
| var b = _p('/test/bin/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| var d = _p('/test/test/d.dart'); |
| provider.newFile( |
| a, |
| r''' |
| import 'package:test/c.dart'; |
| int x = y; |
| '''); |
| provider.newFile( |
| b, |
| r''' |
| import '../lib/c.dart'; |
| int x = y; |
| '''); |
| provider.newFile( |
| c, |
| r''' |
| import '../test/d.dart'; |
| var y = z; |
| '''); |
| provider.newFile( |
| d, |
| r''' |
| String z = "string"; |
| '''); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| driver.addFile(d); |
| |
| // Analysis of my_pkg/bin/a.dart produces no error because |
| // file:///my_pkg/bin/a.dart imports package:my_pkg/c.dart, and |
| // package:my_pkg/c.dart's import is erroneous, causing y's reference to z |
| // to be unresolved (and therefore have type dynamic). |
| { |
| AnalysisResult result = await driver.getResult(a); |
| expect(result.errors, isEmpty); |
| } |
| |
| // Analysis of my_pkg/bin/b.dart produces the error "A value of type |
| // 'String' can't be assigned to a variable of type 'int'", because |
| // file:///my_pkg/bin/b.dart imports file:///my_pkg/lib/c.dart, which |
| // successfully imports file:///my_pkg/test/d.dart, causing y to have an |
| // inferred type of String. |
| { |
| AnalysisResult result = await driver.getResult(b); |
| List<AnalysisError> errors = result.errors; |
| expect(errors, hasLength(1)); |
| expect(errors[0].errorCode, StaticTypeWarningCode.INVALID_ASSIGNMENT); |
| } |
| } |
| |
| test_getResult_noErrors_ifNotAdded() async { |
| var a = _p('/test/lib/a.dart'); |
| provider.newFile(a, 'A a = null;'); |
| |
| AnalysisResult result = await driver.getResult(a); |
| expect(result.errors, isEmpty); |
| } |
| |
| test_getResult_notDartFile() async { |
| var path = _p('/test/lib/test.txt'); |
| provider.newFile(path, 'class A {}'); |
| |
| AnalysisResult result = await driver.getResult(path); |
| expect(result, isNotNull); |
| expect(result.unit.element.types.map((e) => e.name), ['A']); |
| } |
| |
| test_getResult_sameFile_twoUris() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/test/c.dart'); |
| provider.newFile(a, 'class A<T> {}'); |
| provider.newFile( |
| b, |
| r''' |
| import 'a.dart'; |
| var VB = new A<int>(); |
| '''); |
| provider.newFile( |
| c, |
| r''' |
| import '../lib/a.dart'; |
| var VC = new A<double>(); |
| '''); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| await driver.waitForIdle(); |
| |
| { |
| AnalysisResult result = await driver.getResult(b); |
| expect(_getImportSource(result.unit, 0).uri.toString(), |
| 'package:test/a.dart'); |
| expect(_getTopLevelVarType(result.unit, 'VB'), 'A<int>'); |
| } |
| |
| { |
| AnalysisResult result = await driver.getResult(c); |
| expect(_getImportSource(result.unit, 0).uri, |
| provider.pathContext.toUri(_p('/test/lib/a.dart'))); |
| expect(_getTopLevelVarType(result.unit, 'VC'), 'A<double>'); |
| } |
| } |
| |
| test_getResult_selfConsistent() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| provider.newFile( |
| a, |
| r''' |
| import 'b.dart'; |
| var A1 = 1; |
| var A2 = B1; |
| '''); |
| provider.newFile( |
| b, |
| r''' |
| import 'a.dart'; |
| var B1 = A1; |
| '''); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| await driver.waitForIdle(); |
| |
| { |
| AnalysisResult result = await driver.getResult(a); |
| expect(_getTopLevelVarType(result.unit, 'A1'), 'int'); |
| expect(_getTopLevelVarType(result.unit, 'A2'), 'int'); |
| } |
| |
| // Update "a" so that "A1" is now "double". |
| // Get result for "a". |
| // |
| // Even though we have not notified the driver about the change, |
| // we still get "double" for "A1", because getResult() re-read the content. |
| // |
| // We also get "double" for "A2", even though "A2" has the type from "b". |
| // That's because we check for "a" API signature consistency, and because |
| // it has changed, we invalidated the dependency cache, relinked libraries |
| // and recomputed types. |
| provider.updateFile( |
| a, |
| r''' |
| import 'b.dart'; |
| var A1 = 1.2; |
| var A2 = B1; |
| '''); |
| { |
| AnalysisResult result = await driver.getResult(a); |
| expect(_getTopLevelVarType(result.unit, 'A1'), 'double'); |
| expect(_getTopLevelVarType(result.unit, 'A2'), 'double'); |
| } |
| } |
| |
| test_getResult_thenRemove() async { |
| addTestFile('main() {}', priority: true); |
| |
| Future<AnalysisResult> resultFuture = driver.getResult(testFile); |
| driver.removeFile(testFile); |
| |
| AnalysisResult result = await resultFuture; |
| expect(result, isNotNull); |
| expect(result.path, testFile); |
| expect(result.unit, isNotNull); |
| } |
| |
| test_getResult_twoPendingFutures() async { |
| String content = 'main() {}'; |
| addTestFile(content, priority: true); |
| |
| Future<AnalysisResult> future1 = driver.getResult(testFile); |
| Future<AnalysisResult> future2 = driver.getResult(testFile); |
| |
| // Both futures complete, with the same result. |
| AnalysisResult result1 = await future1; |
| AnalysisResult result2 = await future2; |
| expect(result2, same(result1)); |
| expect(result1.path, testFile); |
| expect(result1.unit, isNotNull); |
| } |
| |
| test_getTopLevelNameDeclarations() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| var d = _p('/test/lib/d.dart'); |
| |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'export "a.dart", class B {}'); |
| provider.newFile(c, 'import "d.dart"; class C {}'); |
| provider.newFile(d, 'class D {}'); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| // Don't add d.dart, it is referenced implicitly. |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('A'), [a, b], [false, true]); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('B'), [b], [false]); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('C'), [c], [false]); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('D'), [d], [false]); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('X'), [], []); |
| } |
| |
| test_getTopLevelNameDeclarations_parts() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| |
| provider.newFile( |
| a, |
| r''' |
| library lib; |
| part 'b.dart'; |
| part 'c.dart'; |
| class A {} |
| '''); |
| provider.newFile(b, 'part of lib; class B {}'); |
| provider.newFile(c, 'part of lib; class C {}'); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('A'), [a], [false]); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('B'), [a], [false]); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('C'), [a], [false]); |
| |
| _assertTopLevelDeclarations( |
| await driver.getTopLevelNameDeclarations('X'), [], []); |
| } |
| |
| test_getUnitElement() async { |
| String content = r''' |
| foo(int p) {} |
| main() { |
| foo(42); |
| } |
| '''; |
| addTestFile(content); |
| |
| CompilationUnitElement unitElement = await driver.getUnitElement(testFile); |
| expect(unitElement, isNotNull); |
| expect(unitElement.source.fullName, testFile); |
| expect(unitElement.functions.map((c) => c.name), |
| unorderedEquals(['foo', 'main'])); |
| } |
| |
| test_getUnitElement_notDart() async { |
| var path = _p('/test.txt'); |
| provider.newFile(path, 'class A {}'); |
| CompilationUnitElement unit = await driver.getUnitElement(path); |
| expect(unit, isNotNull); |
| expect(unit.types.map((e) => e.name), ['A']); |
| } |
| |
| test_hasFilesToAnalyze() async { |
| // No files yet, nothing to analyze. |
| expect(driver.hasFilesToAnalyze, isFalse); |
| |
| // Add a new file, it should be analyzed. |
| addTestFile('main() {}', priority: false); |
| expect(driver.hasFilesToAnalyze, isTrue); |
| |
| // Wait for idle, nothing to do. |
| await driver.waitForIdle(); |
| expect(driver.hasFilesToAnalyze, isFalse); |
| |
| // Ask to analyze the file, so there is a file to analyze. |
| Future<AnalysisResult> future = driver.getResult(testFile); |
| expect(driver.hasFilesToAnalyze, isTrue); |
| |
| // Once analysis is done, there is nothing to analyze. |
| await future; |
| expect(driver.hasFilesToAnalyze, isFalse); |
| |
| // Change a file, even if not added, it still might affect analysis. |
| driver.changeFile(_p('/not/added.dart')); |
| expect(driver.hasFilesToAnalyze, isTrue); |
| await driver.waitForIdle(); |
| expect(driver.hasFilesToAnalyze, isFalse); |
| |
| // Request of referenced names is not analysis of a file. |
| driver.getFilesReferencingName('X'); |
| expect(driver.hasFilesToAnalyze, isFalse); |
| } |
| |
| test_hermetic_modifyLibraryFile_resolvePart() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| |
| provider.newFile( |
| a, |
| r''' |
| library a; |
| part 'b.dart'; |
| class C { |
| int foo; |
| } |
| '''); |
| provider.newFile( |
| b, |
| r''' |
| part of a; |
| var c = new C(); |
| '''); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| |
| await driver.getResult(b); |
| |
| // Modify the library, but don't notify the driver. |
| // The driver should use the previous library content and elements. |
| provider.newFile( |
| a, |
| r''' |
| library a; |
| part 'b.dart'; |
| class C { |
| int bar; |
| } |
| '''); |
| |
| var result = await driver.getResult(b); |
| var c = _getTopLevelVar(result.unit, 'c'); |
| var typeC = c.element.type as InterfaceType; |
| // The class C has an old field 'foo', not the new 'bar'. |
| expect(typeC.element.getField('foo'), isNotNull); |
| expect(typeC.element.getField('bar'), isNull); |
| } |
| |
| test_hermetic_overlayOnly_part() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| contentOverlay[a] = r''' |
| library a; |
| part 'b.dart'; |
| class A {} |
| var b = new B(); |
| '''; |
| contentOverlay[b] = 'part of a; class B {}'; |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| |
| AnalysisResult result = await driver.getResult(a); |
| expect(result.errors, isEmpty); |
| expect(_getTopLevelVarType(result.unit, 'b'), 'B'); |
| } |
| |
| test_knownFiles() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| |
| provider.newFile( |
| a, |
| r''' |
| import 'b.dart'; |
| '''); |
| provider.newFile(b, ''); |
| provider.newFile(c, ''); |
| |
| driver.addFile(a); |
| driver.addFile(c); |
| await driver.waitForIdle(); |
| |
| expect(driver.knownFiles, contains(a)); |
| expect(driver.knownFiles, contains(b)); |
| expect(driver.knownFiles, contains(c)); |
| |
| // Remove a.dart and analyze. |
| // Both a.dart and b.dart are not known now. |
| driver.removeFile(a); |
| await driver.waitForIdle(); |
| expect(driver.knownFiles, isNot(contains(a))); |
| expect(driver.knownFiles, isNot(contains(b))); |
| expect(driver.knownFiles, contains(c)); |
| } |
| |
| test_knownFiles_beforeAnalysis() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| |
| provider.newFile(a, ''); |
| |
| // 'a.dart' is added, but not processed yet. |
| // So, the set of known files is empty yet. |
| driver.addFile(a); |
| expect(driver.knownFiles, isEmpty); |
| |
| // Remove 'a.dart'. |
| // It has been no analysis yet, so 'a.dart' is not in the file state, only |
| // in 'added' files. So, it disappears when removed. |
| driver.removeFile(a); |
| expect(driver.knownFiles, isNot(contains(a))); |
| expect(driver.knownFiles, isNot(contains(b))); |
| } |
| |
| test_parseFile_notDart() async { |
| var p = _p('/test/bin/a.txt'); |
| provider.newFile(p, 'class A {}'); |
| |
| ParseResult parseResult = await driver.parseFile(p); |
| expect(parseResult, isNotNull); |
| expect(driver.knownFiles, contains(p)); |
| } |
| |
| test_parseFile_shouldRefresh() async { |
| var p = _p('/test/bin/a.dart'); |
| |
| provider.newFile(p, 'class A {}'); |
| driver.addFile(p); |
| |
| // Get the result, so force the file reading. |
| await driver.getResult(p); |
| |
| // Update the file. |
| provider.newFile(p, 'class A2 {}'); |
| |
| ParseResult parseResult = await driver.parseFile(p); |
| var clazz = parseResult.unit.declarations[0] as ClassDeclaration; |
| expect(clazz.name.name, 'A2'); |
| } |
| |
| test_part_getResult_afterLibrary() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile( |
| a, |
| r''' |
| library a; |
| import 'b.dart'; |
| part 'c.dart'; |
| class A {} |
| var c = new C(); |
| '''); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile( |
| c, |
| r''' |
| part of a; |
| class C {} |
| var a = new A(); |
| var b = new B(); |
| '''); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| |
| // Process a.dart so that we know that it's a library for c.dart later. |
| { |
| AnalysisResult result = await driver.getResult(a); |
| expect(result.errors, isEmpty); |
| expect(_getTopLevelVarType(result.unit, 'c'), 'C'); |
| } |
| |
| // Now c.dart can be resolved without errors in the context of a.dart |
| { |
| AnalysisResult result = await driver.getResult(c); |
| expect(result.errors, isEmpty); |
| expect(_getTopLevelVarType(result.unit, 'a'), 'A'); |
| expect(_getTopLevelVarType(result.unit, 'b'), 'B'); |
| } |
| } |
| |
| test_part_getResult_beforeLibrary() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile( |
| a, |
| r''' |
| library a; |
| import 'b.dart'; |
| part 'c.dart'; |
| class A {} |
| var c = new C(); |
| '''); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile( |
| c, |
| r''' |
| part of a; |
| class C {} |
| var a = new A(); |
| var b = new B(); |
| '''); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| |
| // b.dart will be analyzed after a.dart is analyzed. |
| // So, A and B references are resolved. |
| AnalysisResult result = await driver.getResult(c); |
| expect(result.errors, isEmpty); |
| expect(_getTopLevelVarType(result.unit, 'a'), 'A'); |
| expect(_getTopLevelVarType(result.unit, 'b'), 'B'); |
| } |
| |
| test_part_getResult_noLibrary() async { |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile( |
| c, |
| r''' |
| part of a; |
| class C {} |
| var a = new A(); |
| var b = new B(); |
| '''); |
| |
| driver.addFile(c); |
| |
| // There is no library which c.dart is a part of, so it has unresolved |
| // A and B references. |
| AnalysisResult result = await driver.getResult(c); |
| expect(result.errors, isNotEmpty); |
| expect(result.unit, isNotNull); |
| } |
| |
| test_part_results_afterLibrary() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile( |
| a, |
| r''' |
| library a; |
| import 'b.dart'; |
| part 'c.dart'; |
| class A {} |
| var c = new C(); |
| '''); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile( |
| c, |
| r''' |
| part of a; |
| class C {} |
| var a = new A(); |
| var b = new B(); |
| '''); |
| |
| // The order is important for creating the test case. |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| |
| { |
| await driver.waitForIdle(); |
| |
| // c.dart was added after a.dart, so it is analyzed after a.dart, |
| // so we know that a.dart is the library of c.dart, so no errors. |
| AnalysisResult result = allResults.lastWhere((r) => r.path == c); |
| expect(result.errors, isEmpty); |
| expect(result.unit, isNull); |
| } |
| |
| // Update a.dart so that c.dart is not a part. |
| { |
| provider.updateFile(a, '// does not use c.dart anymore'); |
| driver.changeFile(a); |
| await driver.waitForIdle(); |
| |
| // Now c.dart does not have a library context, so A and B cannot be |
| // resolved, so there are errors. |
| AnalysisResult result = allResults.lastWhere((r) => r.path == c); |
| expect(result.errors, isNotEmpty); |
| expect(result.unit, isNull); |
| } |
| } |
| |
| test_part_results_beforeLibrary() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile( |
| a, |
| r''' |
| library a; |
| import 'b.dart'; |
| part 'c.dart'; |
| class A {} |
| var c = new C(); |
| '''); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile( |
| c, |
| r''' |
| part of a; |
| class C {} |
| var a = new A(); |
| var b = new B(); |
| '''); |
| |
| // The order is important for creating the test case. |
| driver.addFile(c); |
| driver.addFile(a); |
| driver.addFile(b); |
| |
| await driver.waitForIdle(); |
| |
| // c.dart was added before a.dart, so we attempt to analyze it before |
| // a.dart, but we cannot find the library for it, so we delay analysis |
| // until all other files are analyzed, including a.dart, after which we |
| // analyze the delayed parts. |
| AnalysisResult result = allResults.lastWhere((r) => r.path == c); |
| expect(result.errors, isEmpty); |
| expect(result.unit, isNull); |
| } |
| |
| test_part_results_noLibrary() async { |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile( |
| c, |
| r''' |
| part of a; |
| class C {} |
| var a = new A(); |
| var b = new B(); |
| '''); |
| |
| driver.addFile(c); |
| |
| await driver.waitForIdle(); |
| |
| // There is no library which c.dart is a part of, so it has unresolved |
| // A and B references. |
| AnalysisResult result = allResults.lastWhere((r) => r.path == c); |
| expect(result.errors, isNotEmpty); |
| expect(result.unit, isNull); |
| } |
| |
| test_part_results_priority_beforeLibrary() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile( |
| a, |
| r''' |
| library a; |
| import 'b.dart'; |
| part 'c.dart'; |
| class A {} |
| var c = new C(); |
| '''); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile( |
| c, |
| r''' |
| part of a; |
| class C {} |
| var a = new A(); |
| var b = new B(); |
| '''); |
| |
| // The order is important for creating the test case. |
| driver.priorityFiles = [c]; |
| driver.addFile(c); |
| driver.addFile(a); |
| driver.addFile(b); |
| |
| await driver.waitForIdle(); |
| |
| // c.dart was added before a.dart, so we attempt to analyze it before |
| // a.dart, but we cannot find the library for it, so we delay analysis |
| // until all other files are analyzed, including a.dart, after which we |
| // analyze the delayed parts. |
| AnalysisResult result = allResults.lastWhere((r) => r.path == c); |
| expect(result.errors, isEmpty); |
| expect(result.unit, isNotNull); |
| } |
| |
| test_removeFile_changeFile_implicitlyAnalyzed() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| provider.newFile( |
| a, |
| r''' |
| import 'b.dart'; |
| var A = B; |
| '''); |
| provider.newFile(b, 'var B = 1;'); |
| |
| driver.priorityFiles = [a, b]; |
| driver.addFile(a); |
| driver.addFile(b); |
| |
| // We have results for both "a" and "b". |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(2)); |
| { |
| AnalysisResult ar = allResults.firstWhere((r) => r.path == a); |
| expect(_getTopLevelVarType(ar.unit, 'A'), 'int'); |
| } |
| { |
| AnalysisResult br = allResults.firstWhere((r) => r.path == b); |
| expect(_getTopLevelVarType(br.unit, 'B'), 'int'); |
| } |
| allResults.clear(); |
| |
| // Remove "b" and send the change notification. |
| provider.updateFile(b, 'var B = 1.2;'); |
| driver.removeFile(b); |
| driver.changeFile(b); |
| |
| // While "b" is not analyzed explicitly, it is analyzed implicitly. |
| // We don't get a result for "b". |
| // But the change causes "a" to be reanalyzed. |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| { |
| AnalysisResult ar = allResults.firstWhere((r) => r.path == a); |
| expect(_getTopLevelVarType(ar.unit, 'A'), 'double'); |
| } |
| } |
| |
| test_removeFile_changeFile_notAnalyzed() async { |
| addTestFile('main() {}'); |
| |
| // We have a result. |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| expect(allResults[0].path, testFile); |
| allResults.clear(); |
| |
| // Remove the file and send the change notification. |
| // The change notification does nothing, because the file is explicitly |
| // or implicitly analyzed. |
| driver.removeFile(testFile); |
| driver.changeFile(testFile); |
| |
| await driver.waitForIdle(); |
| expect(allResults, isEmpty); |
| } |
| |
| test_removeFile_invalidate_importers() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, "import 'a.dart'; var a = new A();"); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| await driver.waitForIdle(); |
| |
| // b.dart s clean. |
| expect(allResults.singleWhere((r) => r.path == b).errors, isEmpty); |
| allResults.clear(); |
| |
| // Remove a.dart, now b.dart should be reanalyzed and has an error. |
| provider.deleteFile(a); |
| driver.removeFile(a); |
| await driver.waitForIdle(); |
| expect(allResults.singleWhere((r) => r.path == b).errors, hasLength(2)); |
| allResults.clear(); |
| } |
| |
| test_results_priority() async { |
| String content = 'int f() => 42;'; |
| addTestFile(content, priority: true); |
| |
| await driver.waitForIdle(); |
| |
| expect(allResults, hasLength(1)); |
| AnalysisResult result = allResults.single; |
| expect(result.path, testFile); |
| expect(result.uri.toString(), 'package:test/test.dart'); |
| expect(result.content, content); |
| expect(result.contentHash, _md5(content)); |
| expect(result.unit, isNotNull); |
| expect(result.errors, hasLength(0)); |
| |
| var f = result.unit.declarations[0] as FunctionDeclaration; |
| expect(f.name.staticType.toString(), '() → int'); |
| expect(f.returnType.type.toString(), 'int'); |
| } |
| |
| test_results_priorityFirst() async { |
| var a = _p('/test/lib/a.dart'); |
| var b = _p('/test/lib/b.dart'); |
| var c = _p('/test/lib/c.dart'); |
| provider.newFile(a, 'class A {}'); |
| provider.newFile(b, 'class B {}'); |
| provider.newFile(c, 'class C {}'); |
| |
| driver.addFile(a); |
| driver.addFile(b); |
| driver.addFile(c); |
| driver.priorityFiles = [b]; |
| await driver.waitForIdle(); |
| |
| expect(allResults, hasLength(3)); |
| AnalysisResult result = allResults[0]; |
| expect(result.path, b); |
| expect(result.unit, isNotNull); |
| expect(result.errors, hasLength(0)); |
| } |
| |
| test_results_regular() async { |
| String content = 'int f() => 42;'; |
| addTestFile(content); |
| await driver.waitForIdle(); |
| |
| expect(allResults, hasLength(1)); |
| AnalysisResult result = allResults.single; |
| expect(result.path, testFile); |
| expect(result.uri.toString(), 'package:test/test.dart'); |
| expect(result.content, isNull); |
| expect(result.contentHash, _md5(content)); |
| expect(result.unit, isNull); |
| expect(result.errors, hasLength(0)); |
| } |
| |
| test_results_status() async { |
| addTestFile('int f() => 42;'); |
| await driver.waitForIdle(); |
| |
| expect(allStatuses, hasLength(2)); |
| expect(allStatuses[0].isAnalyzing, isTrue); |
| expect(allStatuses[0].isIdle, isFalse); |
| expect(allStatuses[1].isAnalyzing, isFalse); |
| expect(allStatuses[1].isIdle, isTrue); |
| } |
| |
| test_waitForIdle() async { |
| // With no analysis to do, driver.waitForIdle should complete immediately. |
| await driver.waitForIdle(); |
| // Now schedule some analysis. |
| addTestFile('int f() => 42;'); |
| expect(allResults, isEmpty); |
| // driver.waitForIdle should wait for the analysis. |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| // Make sure there is no more analysis pending. |
| await driver.waitForIdle(); |
| expect(allResults, hasLength(1)); |
| } |
| |
| void _assertTopLevelDeclarations( |
| List<TopLevelDeclarationInSource> declarations, |
| List<String> expectedFiles, |
| List<bool> expectedIsExported) { |
| expect(expectedFiles, hasLength(expectedIsExported.length)); |
| for (int i = 0; i < expectedFiles.length; i++) { |
| expect(declarations, |
| contains(predicate((TopLevelDeclarationInSource declaration) { |
| return declaration.source.fullName == expectedFiles[i] && |
| declaration.isExported == expectedIsExported[i]; |
| }))); |
| } |
| } |
| |
| ClassDeclaration _getClass(CompilationUnit unit, String name) { |
| for (CompilationUnitMember declaration in unit.declarations) { |
| if (declaration is ClassDeclaration) { |
| if (declaration.name.name == name) { |
| return declaration; |
| } |
| } |
| } |
| fail('Cannot find the class $name in\n$unit'); |
| return null; |
| } |
| |
| VariableDeclaration _getClassField( |
| CompilationUnit unit, String className, String fieldName) { |
| ClassDeclaration classDeclaration = _getClass(unit, className); |
| for (ClassMember declaration in classDeclaration.members) { |
| if (declaration is FieldDeclaration) { |
| for (var field in declaration.fields.variables) { |
| if (field.name.name == fieldName) { |
| return field; |
| } |
| } |
| } |
| } |
| fail('Cannot find the field $fieldName in the class $className in\n$unit'); |
| return null; |
| } |
| |
| String _getClassFieldType( |
| CompilationUnit unit, String className, String fieldName) { |
| return resolutionMap |
| .elementDeclaredByVariableDeclaration( |
| _getClassField(unit, className, fieldName)) |
| .type |
| .toString(); |
| } |
| |
| MethodDeclaration _getClassMethod( |
| CompilationUnit unit, String className, String methodName) { |
| ClassDeclaration classDeclaration = _getClass(unit, className); |
| for (ClassMember declaration in classDeclaration.members) { |
| if (declaration is MethodDeclaration && |
| declaration.name.name == methodName) { |
| return declaration; |
| } |
| } |
| fail('Cannot find the method $methodName in the class $className in\n' |
| '$unit'); |
| return null; |
| } |
| |
| String _getClassMethodReturnType( |
| CompilationUnit unit, String className, String fieldName) { |
| return resolutionMap |
| .elementDeclaredByMethodDeclaration( |
| _getClassMethod(unit, className, fieldName)) |
| .type |
| .returnType |
| .toString(); |
| } |
| |
| ImportElement _getImportElement(CompilationUnit unit, int directiveIndex) { |
| var import = unit.directives[directiveIndex] as ImportDirective; |
| return import.element as ImportElement; |
| } |
| |
| Source _getImportSource(CompilationUnit unit, int directiveIndex) { |
| return _getImportElement(unit, directiveIndex).importedLibrary.source; |
| } |
| |
| VariableDeclaration _getTopLevelVar(CompilationUnit unit, String name) { |
| for (CompilationUnitMember declaration in unit.declarations) { |
| if (declaration is TopLevelVariableDeclaration) { |
| for (VariableDeclaration variable in declaration.variables.variables) { |
| if (variable.name.name == name) { |
| return variable; |
| } |
| } |
| } |
| } |
| fail('Cannot find the top-level variable $name in\n$unit'); |
| return null; |
| } |
| |
| String _getTopLevelVarType(CompilationUnit unit, String name) { |
| return resolutionMap |
| .elementDeclaredByVariableDeclaration(_getTopLevelVar(unit, name)) |
| .type |
| .toString(); |
| } |
| |
| /** |
| * Return the [provider] specific path for the given Posix [path]. |
| */ |
| String _p(String path) => provider.convertPath(path); |
| |
| static String _md5(String content) { |
| return hex.encode(md5.convert(UTF8.encode(content)).bytes); |
| } |
| } |