| // 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. |
| |
| import 'dart:async'; |
| |
| import 'package:analysis_server/src/services/index/index.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/summary/idl.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| import 'package:typed_mock/typed_mock.dart'; |
| |
| import '../../abstract_single_unit.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(IndexTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class IndexTest extends AbstractSingleUnitTest { |
| Index index = createMemoryIndex(); |
| |
| /** |
| * Return the [Location] with given properties, or fail. |
| */ |
| Location findLocation(List<Location> locations, String libraryUri, |
| String unitUri, int offset, int length, bool isQualified) { |
| for (Location location in locations) { |
| if (location.libraryUri == libraryUri && |
| location.unitUri == unitUri && |
| location.offset == offset && |
| location.length == length && |
| location.isQualified == isQualified) { |
| return location; |
| } |
| } |
| fail('No at $offset with length $length qualified=$isQualified in\n' |
| '${locations.join('\n')}'); |
| return null; |
| } |
| |
| /** |
| * Return the [Location] with given properties, or fail. |
| */ |
| Location findLocationSource( |
| List<Location> locations, Source source, String search, bool isQualified, |
| {int length}) { |
| String code = source.contents.data; |
| int offset = code.indexOf(search); |
| expect(offset, isNonNegative, reason: 'Not found "$search" in\n$code'); |
| length ??= getLeadingIdentifierLength(search); |
| String uri = source.uri.toString(); |
| return findLocation(locations, uri, uri, offset, length, isQualified); |
| } |
| |
| /** |
| * Return the [Location] with given properties, or fail. |
| */ |
| Location findLocationTest( |
| List<Location> locations, String search, bool isQualified, |
| {int length}) { |
| int offset = findOffset(search); |
| length ??= getLeadingIdentifierLength(search); |
| String testUri = testSource.uri.toString(); |
| return findLocation( |
| locations, testUri, testUri, offset, length, isQualified); |
| } |
| |
| void setUp() { |
| super.setUp(); |
| } |
| |
| void tearDown() { |
| super.tearDown(); |
| index = null; |
| } |
| |
| test_getDefinedNames_classMember() async { |
| await _indexTestUnit(''' |
| class A { |
| test() {} |
| } |
| class B { |
| int test = 1; |
| main() { |
| int test = 2; |
| } |
| } |
| '''); |
| ClassElement classA = findElement('A'); |
| ClassElement classB = findElement('B'); |
| List<Location> locations = await index.getDefinedNames( |
| new RegExp(r'^test$'), IndexNameKind.classMember); |
| expect(locations, hasLength(2)); |
| _assertHasDefinedName(locations, classA.methods[0]); |
| _assertHasDefinedName(locations, classB.fields[0]); |
| } |
| |
| test_getDefinedNames_topLevel() async { |
| await _indexTestUnit(''' |
| class A {} // A |
| class B = Object with A; |
| typedef C(); |
| D() {} |
| var E = null; |
| class NoMatchABCDE {} |
| '''); |
| Element topA = findElement('A'); |
| Element topB = findElement('B'); |
| Element topC = findElement('C'); |
| Element topD = findElement('D'); |
| Element topE = findElement('E'); |
| List<Location> locations = await index.getDefinedNames( |
| new RegExp(r'^[A-E]$'), IndexNameKind.topLevel); |
| expect(locations, hasLength(5)); |
| _assertHasDefinedName(locations, topA); |
| _assertHasDefinedName(locations, topB); |
| _assertHasDefinedName(locations, topC); |
| _assertHasDefinedName(locations, topD); |
| _assertHasDefinedName(locations, topE); |
| } |
| |
| test_getDefinedNames_topLevel2() async { |
| await _indexTestUnit( |
| ''' |
| class A {} // A |
| class B = Object with A; |
| class NoMatchABCDE {} |
| ''', |
| declOnly: true); |
| Element topA = findElement('A'); |
| Element topB = findElement('B'); |
| List<Location> locations = await index.getDefinedNames( |
| new RegExp(r'^[A-E]$'), IndexNameKind.topLevel); |
| expect(locations, hasLength(2)); |
| _assertHasDefinedName(locations, topA); |
| _assertHasDefinedName(locations, topB); |
| } |
| |
| test_getRelations_isExtendedBy() async { |
| await _indexTestUnit(r''' |
| class A {} |
| class B extends A {} // B |
| '''); |
| Source source2 = await _indexUnit( |
| '/test2.dart', |
| r''' |
| import 'test.dart'; |
| class C extends A {} // C |
| '''); |
| ClassElement elementA = testUnitElement.getType('A'); |
| List<Location> locations = |
| await index.getRelations(elementA, IndexRelationKind.IS_EXTENDED_BY); |
| findLocationTest(locations, 'A {} // B', false); |
| findLocationSource(locations, source2, 'A {} // C', false); |
| } |
| |
| test_getRelations_isReferencedBy() async { |
| await _indexTestUnit(r''' |
| main(int a, int b) { |
| } |
| '''); |
| ClassElement intElement = context.typeProvider.intType.element; |
| List<Location> locations = await index.getRelations( |
| intElement, IndexRelationKind.IS_REFERENCED_BY); |
| findLocationTest(locations, 'int a', false); |
| findLocationTest(locations, 'int b', false); |
| } |
| |
| test_getUnresolvedMemberReferences_qualified_resolved() async { |
| await _indexTestUnit(''' |
| class A { |
| var test; // A |
| } |
| main(A a) { |
| print(a.test); |
| a.test = 1; |
| a.test += 2; |
| a.test(); |
| } |
| '''); |
| List<Location> locations = |
| await index.getUnresolvedMemberReferences('test'); |
| expect(locations, isEmpty); |
| } |
| |
| test_getUnresolvedMemberReferences_qualified_unresolved() async { |
| await _indexTestUnit(''' |
| class A { |
| var test; // A |
| } |
| main(p) { |
| print(p.test); |
| p.test = 1; |
| p.test += 2; |
| p.test(); |
| print(p.test2); // not requested |
| } |
| '''); |
| List<Location> locations = |
| await index.getUnresolvedMemberReferences('test'); |
| expect(locations, hasLength(4)); |
| findLocationTest(locations, 'test);', true); |
| findLocationTest(locations, 'test = 1;', true); |
| findLocationTest(locations, 'test += 2;', true); |
| findLocationTest(locations, 'test();', true); |
| } |
| |
| test_getUnresolvedMemberReferences_unqualified_resolved() async { |
| await _indexTestUnit(''' |
| class A { |
| var test; |
| m() { |
| print(test); |
| test = 1; |
| test += 2; |
| test(); |
| } |
| } |
| '''); |
| List<Location> locations = |
| await index.getUnresolvedMemberReferences('test'); |
| expect(locations, isEmpty); |
| } |
| |
| test_getUnresolvedMemberReferences_unqualified_unresolved() async { |
| verifyNoTestUnitErrors = false; |
| await _indexTestUnit(''' |
| class A { |
| m() { |
| print(test); |
| test = 1; |
| test += 2; |
| test(); |
| print(test2); // not requested |
| } |
| } |
| '''); |
| List<Location> locations = |
| await index.getUnresolvedMemberReferences('test'); |
| expect(locations, hasLength(4)); |
| findLocationTest(locations, 'test);', false); |
| findLocationTest(locations, 'test = 1;', false); |
| findLocationTest(locations, 'test += 2;', false); |
| findLocationTest(locations, 'test();', false); |
| } |
| |
| test_indexDeclarations_afterIndexUnit() async { |
| await resolveTestUnit(''' |
| var a = 0; |
| var b = a + 1; |
| '''); |
| index.indexUnit(testUnit); |
| TopLevelVariableElement a = findElement('a'); |
| // We can find references. |
| { |
| List<Location> locations = await index.getRelations( |
| a.getter, IndexRelationKind.IS_REFERENCED_BY); |
| findLocationTest(locations, 'a + 1', false); |
| } |
| // Attempt to index just declarations - we still can find references. |
| index.indexDeclarations(testUnit); |
| { |
| List<Location> locations = await index.getRelations( |
| a.getter, IndexRelationKind.IS_REFERENCED_BY); |
| findLocationTest(locations, 'a + 1', false); |
| } |
| } |
| |
| test_indexDeclarations_nullUnit() async { |
| index.indexDeclarations(null); |
| } |
| |
| test_indexDeclarations_nullUnitElement() async { |
| await resolveTestUnit(''); |
| testUnit.element = null; |
| index.indexDeclarations(testUnit); |
| } |
| |
| test_indexUnit_nullLibraryElement() async { |
| await resolveTestUnit(''); |
| CompilationUnitElement unitElement = new _CompilationUnitElementMock(); |
| expect(unitElement.library, isNull); |
| testUnit.element = unitElement; |
| index.indexUnit(testUnit); |
| } |
| |
| test_indexUnit_nullUnit() async { |
| index.indexUnit(null); |
| } |
| |
| test_indexUnit_nullUnitElement() async { |
| await resolveTestUnit(''); |
| testUnit.element = null; |
| index.indexUnit(testUnit); |
| } |
| |
| test_removeContext() async { |
| await _indexTestUnit(''' |
| class A {} |
| '''); |
| RegExp regExp = new RegExp(r'^A$'); |
| expect(await index.getDefinedNames(regExp, IndexNameKind.topLevel), |
| hasLength(1)); |
| // remove the context - no top-level declarations |
| index.removeContext(context); |
| expect( |
| await index.getDefinedNames(regExp, IndexNameKind.topLevel), isEmpty); |
| } |
| |
| test_removeUnit() async { |
| RegExp regExp = new RegExp(r'^[AB]$'); |
| Source sourceA = addSource('/a.dart', 'class A {}'); |
| Source sourceB = addSource('/b.dart', 'class B {}'); |
| CompilationUnit unitA = await resolveLibraryUnit(sourceA); |
| CompilationUnit unitB = await resolveLibraryUnit(sourceB); |
| index.indexUnit(unitA); |
| index.indexUnit(unitB); |
| { |
| List<Location> locations = |
| await index.getDefinedNames(regExp, IndexNameKind.topLevel); |
| expect(locations, hasLength(2)); |
| expect(locations.map((l) => l.libraryUri), |
| unorderedEquals([sourceA.uri.toString(), sourceB.uri.toString()])); |
| } |
| // remove a.dart - no a.dart location |
| index.removeUnit(context, sourceA, sourceA); |
| { |
| List<Location> locations = |
| await index.getDefinedNames(regExp, IndexNameKind.topLevel); |
| expect(locations, hasLength(1)); |
| expect(locations.map((l) => l.libraryUri), |
| unorderedEquals([sourceB.uri.toString()])); |
| } |
| } |
| |
| /** |
| * Assert that the given list of [locations] has a [Location] corresponding |
| * to the [element]. |
| */ |
| void _assertHasDefinedName(List<Location> locations, Element element) { |
| String libraryUri = element.library.source.uri.toString(); |
| String unitUri = element.source.uri.toString(); |
| for (Location location in locations) { |
| if (location.libraryUri == libraryUri && |
| location.unitUri == unitUri && |
| location.offset == element.nameOffset && |
| location.length == element.nameLength) { |
| return; |
| } |
| } |
| fail('No declaration of $element at ${element.nameOffset} in\n' |
| '${locations.join('\n')}'); |
| } |
| |
| Future<Null> _indexTestUnit(String code, {bool declOnly: false}) async { |
| await resolveTestUnit(code); |
| if (declOnly) { |
| index.indexDeclarations(testUnit); |
| } else { |
| index.indexUnit(testUnit); |
| } |
| } |
| |
| Future<Source> _indexUnit(String path, String code) async { |
| Source source = addSource(path, code); |
| CompilationUnit unit = await resolveLibraryUnit(source); |
| index.indexUnit(unit); |
| return source; |
| } |
| } |
| |
| class _CompilationUnitElementMock extends TypedMock |
| implements CompilationUnitElement {} |