Chromium Code Reviews| Index: pkg/analyzer/test/src/dart/analysis/index_test.dart |
| diff --git a/pkg/analyzer/test/src/dart/analysis/index_test.dart b/pkg/analyzer/test/src/dart/analysis/index_test.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fd6738e80f8b68a7743b34d6a58bd9a79303f2b8 |
| --- /dev/null |
| +++ b/pkg/analyzer/test/src/dart/analysis/index_test.dart |
| @@ -0,0 +1,1353 @@ |
| +// 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 'dart:convert'; |
| + |
| +import 'package:analyzer/dart/ast/ast.dart'; |
| +import 'package:analyzer/dart/element/element.dart'; |
| +import 'package:analyzer/dart/element/visitor.dart'; |
| +import 'package:analyzer/file_system/file_system.dart'; |
| +import 'package:analyzer/file_system/memory_file_system.dart'; |
| +import 'package:analyzer/source/package_map_resolver.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/index.dart'; |
| +import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; |
| +import 'package:analyzer/src/generated/source.dart'; |
| +import 'package:analyzer/src/summary/format.dart'; |
| +import 'package:analyzer/src/summary/idl.dart'; |
| +import 'package:test/test.dart'; |
| +import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| + |
| +import '../../context/mock_sdk.dart'; |
| + |
| +main() { |
| + defineReflectiveSuite(() { |
| + defineReflectiveTests(IndexTest); |
| + }); |
| +} |
| + |
| +/** |
| + * Finds an [Element] with the given [name]. |
| + */ |
| +Element findChildElement(Element root, String name, [ElementKind kind]) { |
| + Element result = null; |
| + root.accept(new _ElementVisitorFunctionWrapper((Element element) { |
| + if (element.name != name) { |
| + return; |
| + } |
| + if (kind != null && element.kind != kind) { |
| + return; |
| + } |
| + result = element; |
| + })); |
| + return result; |
| +} |
| + |
| +/** |
| + * A function to be called for every [Element]. |
| + */ |
| +typedef void _ElementVisitorFunction(Element element); |
| + |
| +class ExpectedLocation { |
| + final CompilationUnitElement unitElement; |
| + final int offset; |
| + final int length; |
| + final bool isQualified; |
| + |
| + ExpectedLocation( |
| + this.unitElement, this.offset, this.length, this.isQualified); |
| + |
| + @override |
| + String toString() { |
| + return '(unit=$unitElement; offset=$offset; length=$length;' |
| + ' isQualified=$isQualified)'; |
| + } |
| +} |
| + |
| +@reflectiveTest |
| +class IndexTest { |
| + static final MockSdk sdk = new MockSdk(); |
| + |
| + final MemoryResourceProvider provider = new MemoryResourceProvider(); |
| + final ByteStore byteStore = new MemoryByteStore(); |
| + final FileContentOverlay contentOverlay = new FileContentOverlay(); |
| + |
| + final StringBuffer logBuffer = new StringBuffer(); |
| + PerformanceLog logger; |
| + |
| + AnalysisDriverScheduler scheduler; |
| + AnalysisDriver driver; |
| + |
| + String testProject; |
| + String testFile; |
| + |
| + String testCode; |
| + CompilationUnit testUnit; |
| + CompilationUnitElement testUnitElement; |
| + LibraryElement testLibraryElement; |
| + |
| + AnalysisDriverUnitIndex index; |
| + |
| + _ElementIndexAssert assertThat(Element element) { |
| + List<_Relation> relations = _getElementRelations(element); |
| + return new _ElementIndexAssert(this, element, relations); |
| + } |
| + |
| + _NameIndexAssert assertThatName(String name) { |
| + return new _NameIndexAssert(this, name); |
| + } |
| + |
| + Element findElement(String name, [ElementKind kind]) { |
| + return findChildElement(testUnitElement, name, kind); |
| + } |
| + |
| + int findOffset(String search) { |
| + int offset = testCode.indexOf(search); |
| + expect(offset, isNonNegative, reason: "Not found '$search' in\n$testCode"); |
| + return offset; |
| + } |
| + |
| + int getLeadingIdentifierLength(String search) { |
| + int length = 0; |
| + while (length < search.length) { |
| + int c = search.codeUnitAt(length); |
| + if (c >= 'a'.codeUnitAt(0) && c <= 'z'.codeUnitAt(0)) { |
| + length++; |
| + continue; |
| + } |
| + if (c >= 'A'.codeUnitAt(0) && c <= 'Z'.codeUnitAt(0)) { |
| + length++; |
| + continue; |
| + } |
| + if (c >= '0'.codeUnitAt(0) && c <= '9'.codeUnitAt(0)) { |
| + length++; |
| + continue; |
| + } |
| + break; |
| + } |
| + return length; |
| + } |
| + |
| + CompilationUnitElement importedUnit({int index: 0}) { |
| + List<ImportElement> imports = testLibraryElement.imports; |
| + return imports[index].importedLibrary.definingCompilationUnit; |
| + } |
| + |
| + void setUp() { |
| + new MockSdk(); |
| + testProject = _p('/test/lib'); |
| + testFile = _p('/test/lib/test.dart'); |
| + logger = new PerformanceLog(logBuffer); |
| + scheduler = new AnalysisDriverScheduler(logger); |
| + driver = new AnalysisDriver( |
| + scheduler, |
| + logger, |
| + provider, |
| + byteStore, |
| + contentOverlay, |
| + new SourceFactory([ |
| + new DartUriResolver(sdk), |
| + new PackageMapUriResolver(provider, <String, List<Folder>>{ |
| + 'test': [provider.getFolder(testProject)] |
| + }), |
| + new ResourceUriResolver(provider) |
| + ], null, provider), |
| + new AnalysisOptionsImpl()..strongMode = true); |
| + scheduler.start(); |
| +// driver.status.lastWhere((status) { |
|
Paul Berry
2016/11/21 13:46:57
Was this temporary debugging code? Should it be r
scheglov
2016/11/21 17:27:30
Done.
|
| +// allStatuses.add(status); |
| +// if (status.isIdle) { |
| +// idleStatusMonitor.notify(); |
| +// } |
| +// }); |
| +// driver.results.listen(allResults.add); |
| + } |
| + |
| + test_hasAncestor_ClassDeclaration() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +class B1 extends A {} |
| +class B2 implements A {} |
| +class C1 extends B1 {} |
| +class C2 extends B2 {} |
| +class C3 implements B1 {} |
| +class C4 implements B2 {} |
| +class M extends Object with A {} |
| +'''); |
| + ClassElement classElementA = findElement("A"); |
| + assertThat(classElementA) |
| + ..isAncestorOf('B1 extends A') |
| + ..isAncestorOf('B2 implements A') |
| + ..isAncestorOf('C1 extends B1') |
| + ..isAncestorOf('C2 extends B2') |
| + ..isAncestorOf('C3 implements B1') |
| + ..isAncestorOf('C4 implements B2') |
| + ..isAncestorOf('M extends Object with A'); |
| + } |
| + |
| + test_hasAncestor_ClassTypeAlias() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +class B extends A {} |
| +class C1 = Object with A; |
| +class C2 = Object with B; |
| +'''); |
| + ClassElement classElementA = findElement('A'); |
| + ClassElement classElementB = findElement('B'); |
| + assertThat(classElementA) |
| + ..isAncestorOf('C1 = Object with A') |
| + ..isAncestorOf('C2 = Object with B'); |
| + assertThat(classElementB)..isAncestorOf('C2 = Object with B'); |
| + } |
| + |
| + test_isExtendedBy_ClassDeclaration() async { |
| + await _indexTestUnit(''' |
| +class A {} // 1 |
| +class B extends A {} // 2 |
| +'''); |
| + ClassElement elementA = findElement('A'); |
| + assertThat(elementA) |
| + ..isExtendedAt('A {} // 2', false) |
| + ..isReferencedAt('A {} // 2', false); |
| + } |
| + |
| + test_isExtendedBy_ClassDeclaration_isQualified() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +class A {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' as p; |
| +class B extends p.A {} // 2 |
| +'''); |
| + ClassElement elementA = importedUnit().getType('A'); |
| + assertThat(elementA).isExtendedAt('A {} // 2', true); |
| + } |
| + |
| + test_isExtendedBy_ClassDeclaration_Object() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +'''); |
| + ClassElement elementA = findElement('A'); |
| + ClassElement elementObject = elementA.supertype.element; |
| + assertThat(elementObject).isExtendedAt('A {}', true, length: 0); |
| + } |
| + |
| + test_isExtendedBy_ClassTypeAlias() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +class B {} |
| +class C = A with B; |
| +'''); |
| + ClassElement elementA = findElement('A'); |
| + assertThat(elementA) |
| + ..isExtendedAt('A with', false) |
| + ..isReferencedAt('A with', false); |
| + } |
| + |
| + test_isExtendedBy_ClassTypeAlias_isQualified() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +class A {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' as p; |
| +class B {} |
| +class C = p.A with B; |
| +'''); |
| + ClassElement elementA = importedUnit().getType('A'); |
| + assertThat(elementA) |
| + ..isExtendedAt('A with', true) |
| + ..isReferencedAt('A with', true); |
| + } |
| + |
| + test_isImplementedBy_ClassDeclaration() async { |
| + await _indexTestUnit(''' |
| +class A {} // 1 |
| +class B implements A {} // 2 |
| +'''); |
| + ClassElement elementA = findElement('A'); |
| + assertThat(elementA) |
| + ..isImplementedAt('A {} // 2', false) |
| + ..isReferencedAt('A {} // 2', false); |
| + } |
| + |
| + test_isImplementedBy_ClassDeclaration_isQualified() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +class A {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' as p; |
| +class B implements p.A {} // 2 |
| +'''); |
| + ClassElement elementA = importedUnit().getType('A'); |
| + assertThat(elementA) |
| + ..isImplementedAt('A {} // 2', true) |
| + ..isReferencedAt('A {} // 2', true); |
| + } |
| + |
| + test_isImplementedBy_ClassTypeAlias() async { |
| + await _indexTestUnit(''' |
| +class A {} // 1 |
| +class B {} // 2 |
| +class C = Object with A implements B; // 3 |
| +'''); |
| + ClassElement elementB = findElement('B'); |
| + assertThat(elementB) |
| + ..isImplementedAt('B; // 3', false) |
| + ..isReferencedAt('B; // 3', false); |
| + } |
| + |
| + test_isInvokedBy_FieldElement() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + var field; |
| + main() { |
| + this.field(); // q |
| + field(); // nq |
| + } |
| +}'''); |
| + FieldElement field = findElement('field'); |
| + assertThat(field.getter) |
| + ..isInvokedAt('field(); // q', true) |
| + ..isInvokedAt('field(); // nq', false); |
| + } |
| + |
| + test_isInvokedBy_FunctionElement() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +library lib; |
| +foo() {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart'; |
| +import 'lib.dart' as pref; |
| +main() { |
| + pref.foo(); // q |
| + foo(); // nq |
| +}'''); |
| + FunctionElement element = importedUnit().functions[0]; |
| + assertThat(element) |
| + ..isInvokedAt('foo(); // q', true) |
| + ..isInvokedAt('foo(); // nq', false); |
| + } |
| + |
| + test_isInvokedBy_FunctionElement_synthetic_loadLibrary() async { |
| + await _indexTestUnit(''' |
| +import 'dart:math' deferred as math; |
| +main() { |
| + math.loadLibrary(); // 1 |
| + math.loadLibrary(); // 2 |
| +} |
| +'''); |
| + LibraryElement mathLib = testLibraryElement.imports[0].importedLibrary; |
| + FunctionElement element = mathLib.loadLibraryFunction; |
| + assertThat(element).isInvokedAt('loadLibrary(); // 1', true); |
| + assertThat(element).isInvokedAt('loadLibrary(); // 2', true); |
| + } |
| + |
| + test_isInvokedBy_MethodElement() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + foo() {} |
| + main() { |
| + this.foo(); // q |
| + foo(); // nq |
| + } |
| +}'''); |
| + Element element = findElement('foo'); |
| + assertThat(element) |
| + ..isInvokedAt('foo(); // q', true) |
| + ..isInvokedAt('foo(); // nq', false); |
| + } |
| + |
| + test_isInvokedBy_MethodElement_propagatedType() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + foo() {} |
| +} |
| +main() { |
| + var a = new A(); |
| + a.foo(); |
| +} |
| +'''); |
| + Element element = findElement('foo'); |
| + assertThat(element).isInvokedAt('foo();', true); |
| + } |
| + |
| + test_isInvokedBy_operator_binary() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + operator +(other) => this; |
| +} |
| +main(A a) { |
| + print(a + 1); |
| + a += 2; |
| + ++a; |
| + a++; |
| +} |
| +'''); |
| + MethodElement element = findElement('+'); |
| + assertThat(element) |
| + ..isInvokedAt('+ 1', true, length: 1) |
| + ..isInvokedAt('+= 2', true, length: 2) |
| + ..isInvokedAt('++a', true, length: 2) |
| + ..isInvokedAt('++;', true, length: 2); |
| + } |
| + |
| + test_isInvokedBy_operator_index() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + operator [](i) => null; |
| + operator []=(i, v) {} |
| +} |
| +main(A a) { |
| + print(a[0]); |
| + a[1] = 42; |
| +} |
| +'''); |
| + MethodElement readElement = findElement('[]'); |
| + MethodElement writeElement = findElement('[]='); |
| + assertThat(readElement).isInvokedAt('[0]', true, length: 1); |
| + assertThat(writeElement).isInvokedAt('[1]', true, length: 1); |
| + } |
| + |
| + test_isInvokedBy_operator_prefix() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + A operator ~() => this; |
| +} |
| +main(A a) { |
| + print(~a); |
| +} |
| +'''); |
| + MethodElement element = findElement('~'); |
| + assertThat(element).isInvokedAt('~a', true, length: 1); |
| + } |
| + |
| + test_isInvokedBy_PropertyAccessorElement_getter() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + get ggg => null; |
| + main() { |
| + this.ggg(); // q |
| + ggg(); // nq |
| + } |
| +}'''); |
| + PropertyAccessorElement element = findElement('ggg', ElementKind.GETTER); |
| + assertThat(element) |
| + ..isInvokedAt('ggg(); // q', true) |
| + ..isInvokedAt('ggg(); // nq', false); |
| + } |
| + |
| + test_isMixedInBy_ClassDeclaration() async { |
| + await _indexTestUnit(''' |
| +class A {} // 1 |
| +class B extends Object with A {} // 2 |
| +'''); |
| + ClassElement elementA = findElement('A'); |
| + assertThat(elementA) |
| + ..isMixedInAt('A {} // 2', false) |
| + ..isReferencedAt('A {} // 2', false); |
| + } |
| + |
| + test_isMixedInBy_ClassDeclaration_isQualified() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +class A {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' as p; |
| +class B extends Object with p.A {} // 2 |
| +'''); |
| + ClassElement elementA = importedUnit().getType('A'); |
| + assertThat(elementA).isMixedInAt('A {} // 2', true); |
| + } |
| + |
| + test_isMixedInBy_ClassTypeAlias() async { |
| + await _indexTestUnit(''' |
| +class A {} // 1 |
| +class B = Object with A; // 2 |
| +'''); |
| + ClassElement elementA = findElement('A'); |
| + assertThat(elementA).isMixedInAt('A; // 2', false); |
| + } |
| + |
| + test_isReferencedBy_ClassElement() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + static var field; |
| +} |
| +main(A p) { |
| + A v; |
| + new A(); // 2 |
| + A.field = 1; |
| + print(A.field); // 3 |
| +} |
| +'''); |
| + ClassElement element = findElement('A'); |
| + assertThat(element) |
| + ..isReferencedAt('A p) {', false) |
| + ..isReferencedAt('A v;', false) |
| + ..isReferencedAt('A(); // 2', false) |
| + ..isReferencedAt('A.field = 1;', false) |
| + ..isReferencedAt('A.field); // 3', false); |
| + } |
| + |
| + test_isReferencedBy_ClassElement_invocation() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +main() { |
| + A(); // invalid code, but still a reference |
| +}'''); |
| + Element element = findElement('A'); |
| + assertThat(element).isReferencedAt('A();', false); |
| + } |
| + |
| + test_isReferencedBy_ClassElement_invocation_isQualified() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +class A {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' as p; |
| +main() { |
| + p.A(); // invalid code, but still a reference |
| +}'''); |
| + Element element = importedUnit().getType('A'); |
| + assertThat(element).isReferencedAt('A();', true); |
| + } |
| + |
| + test_isReferencedBy_ClassTypeAlias() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +class B = Object with A; |
| +main(B p) { |
| + B v; |
| +} |
| +'''); |
| + ClassElement element = findElement('B'); |
| + assertThat(element) |
| + ..isReferencedAt('B p) {', false) |
| + ..isReferencedAt('B v;', false); |
| + } |
| + |
| + test_isReferencedBy_CompilationUnitElement_export() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +library lib; |
| +'''); |
| + await _indexTestUnit(''' |
| +export 'lib.dart'; |
| +'''); |
| + LibraryElement element = testLibraryElement.exports[0].exportedLibrary; |
| + assertThat(element)..isReferencedAt("'lib.dart'", true, length: 10); |
| + } |
| + |
| + test_isReferencedBy_CompilationUnitElement_import() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +library lib; |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart'; |
| +'''); |
| + LibraryElement element = testLibraryElement.imports[0].importedLibrary; |
| + assertThat(element)..isReferencedAt("'lib.dart'", true, length: 10); |
| + } |
| + |
| + test_isReferencedBy_CompilationUnitElement_part() async { |
| + provider.newFile(_p('$testProject/my_unit.dart'), 'part of my_lib;'); |
| + await _indexTestUnit(''' |
| +library my_lib; |
| +part 'my_unit.dart'; |
| +'''); |
| + CompilationUnitElement element = testLibraryElement.parts[0]; |
| + assertThat(element)..isReferencedAt("'my_unit.dart';", true, length: 14); |
| + } |
| + |
| + test_isReferencedBy_ConstructorElement() async { |
| + await _indexTestUnit(''' |
| +class A implements B { |
| + A() {} |
| + A.foo() {} |
| +} |
| +class B extends A { |
| + B() : super(); // 1 |
| + B.foo() : super.foo(); // 2 |
| + factory B.bar() = A.foo; // 3 |
| +} |
| +main() { |
| + new A(); // 4 |
| + new A.foo(); // 5 |
| +} |
| +'''); |
| + ClassElement classA = findElement('A'); |
| + ConstructorElement constA = classA.constructors[0]; |
| + ConstructorElement constA_foo = classA.constructors[1]; |
| + // A() |
| + assertThat(constA) |
| + ..hasRelationCount(2) |
| + ..isReferencedAt('(); // 1', true, length: 0) |
| + ..isReferencedAt('(); // 4', true, length: 0); |
| + // A.foo() |
| + assertThat(constA_foo) |
| + ..hasRelationCount(3) |
| + ..isReferencedAt('.foo(); // 2', true, length: 4) |
| + ..isReferencedAt('.foo; // 3', true, length: 4) |
| + ..isReferencedAt('.foo(); // 5', true, length: 4); |
| + } |
| + |
| + test_isReferencedBy_ConstructorElement_classTypeAlias() async { |
| + await _indexTestUnit(''' |
| +class M {} |
| +class A implements B { |
| + A() {} |
| + A.named() {} |
| +} |
| +class B = A with M; |
| +class C = B with M; |
| +main() { |
| + new B(); // B1 |
| + new B.named(); // B2 |
| + new C(); // C1 |
| + new C.named(); // C2 |
| +} |
| +'''); |
| + ClassElement classA = findElement('A'); |
| + ConstructorElement constA = classA.constructors[0]; |
| + ConstructorElement constA_named = classA.constructors[1]; |
| + assertThat(constA) |
| + ..isReferencedAt('(); // B1', true, length: 0) |
| + ..isReferencedAt('(); // C1', true, length: 0); |
| + assertThat(constA_named) |
| + ..isReferencedAt('.named(); // B2', true, length: 6) |
| + ..isReferencedAt('.named(); // C2', true, length: 6); |
| + } |
| + |
| + test_isReferencedBy_ConstructorElement_classTypeAlias_cycle() async { |
| + await _indexTestUnit(''' |
| +class M {} |
| +class A = B with M; |
| +class B = A with M; |
| +main() { |
| + new A(); |
| + new B(); |
| +} |
| +'''); |
| + // No additional validation, but it should not fail with stack overflow. |
| + } |
| + |
| + test_isReferencedBy_ConstructorElement_namedOnlyWithDot() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + A.named() {} |
| +} |
| +main() { |
| + new A.named(); |
| +} |
| +'''); |
| + // has ".named()", but does not have "named()" |
| + int offsetWithoutDot = findOffset('named();'); |
| + int offsetWithDot = findOffset('.named();'); |
| + expect(index.usedElementOffsets, isNot(contains(offsetWithoutDot))); |
| + expect(index.usedElementOffsets, contains(offsetWithDot)); |
| + } |
| + |
| + test_isReferencedBy_ConstructorElement_redirection() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + A() : this.bar(); // 1 |
| + A.foo() : this(); // 2 |
| + A.bar(); |
| +} |
| +'''); |
| + ClassElement classA = findElement('A'); |
| + ConstructorElement constA = classA.constructors[0]; |
| + ConstructorElement constA_bar = classA.constructors[2]; |
| + assertThat(constA).isReferencedAt('(); // 2', true, length: 0); |
| + assertThat(constA_bar).isReferencedAt('.bar(); // 1', true, length: 4); |
| + } |
| + |
| + test_isReferencedBy_ConstructorElement_synthetic() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +main() { |
| + new A(); // 1 |
| +} |
| +'''); |
| + ClassElement classA = findElement('A'); |
| + ConstructorElement constA = classA.constructors[0]; |
| + // A() |
| + assertThat(constA)..isReferencedAt('(); // 1', true, length: 0); |
| + } |
| + |
| + test_isReferencedBy_DynamicElement() async { |
| + await _indexTestUnit(''' |
| +dynamic f() { |
| +}'''); |
| + expect(index.usedElementOffsets, isEmpty); |
| + } |
| + |
| + test_isReferencedBy_FieldElement() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + var field; |
| + A({this.field}); |
| + m() { |
| + field = 2; // nq |
| + print(field); // nq |
| + } |
| +} |
| +main(A a) { |
| + a.field = 3; // q |
| + print(a.field); // q |
| + new A(field: 4); |
| +} |
| +'''); |
| + FieldElement field = findElement('field', ElementKind.FIELD); |
| + PropertyAccessorElement getter = field.getter; |
| + PropertyAccessorElement setter = field.setter; |
| + // A() |
| + assertThat(field)..isWrittenAt('field});', true); |
| + // m() |
| + assertThat(setter)..isReferencedAt('field = 2; // nq', false); |
| + assertThat(getter)..isReferencedAt('field); // nq', false); |
| + // main() |
| + assertThat(setter)..isReferencedAt('field = 3; // q', true); |
| + assertThat(getter)..isReferencedAt('field); // q', true); |
| + assertThat(field)..isReferencedAt('field: 4', true); |
| + } |
| + |
| + test_isReferencedBy_FieldElement_multiple() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + var aaa; |
| + var bbb; |
| + A(this.aaa, this.bbb) {} |
| + m() { |
| + print(aaa); |
| + aaa = 1; |
| + print(bbb); |
| + bbb = 2; |
| + } |
| +} |
| +'''); |
| + // aaa |
| + { |
| + FieldElement field = findElement('aaa', ElementKind.FIELD); |
| + PropertyAccessorElement getter = field.getter; |
| + PropertyAccessorElement setter = field.setter; |
| + assertThat(field)..isWrittenAt('aaa, ', true); |
| + assertThat(getter)..isReferencedAt('aaa);', false); |
| + assertThat(setter)..isReferencedAt('aaa = 1;', false); |
| + } |
| + // bbb |
| + { |
| + FieldElement field = findElement('bbb', ElementKind.FIELD); |
| + PropertyAccessorElement getter = field.getter; |
| + PropertyAccessorElement setter = field.setter; |
| + assertThat(field)..isWrittenAt('bbb) {}', true); |
| + assertThat(getter)..isReferencedAt('bbb);', false); |
| + assertThat(setter)..isReferencedAt('bbb = 2;', false); |
| + } |
| + } |
| + |
| + test_isReferencedBy_FieldElement_ofEnum() async { |
| + await _indexTestUnit(''' |
| +enum MyEnum { |
| + A, B, C |
| +} |
| +main() { |
| + print(MyEnum.values); |
| + print(MyEnum.A.index); |
| + print(MyEnum.A); |
| + print(MyEnum.B); |
| +} |
| +'''); |
| + ClassElement enumElement = findElement('MyEnum'); |
| + assertThat(enumElement.getGetter('values')) |
| + ..isReferencedAt('values);', true); |
| + assertThat(enumElement.getGetter('index'))..isReferencedAt('index);', true); |
| + assertThat(enumElement.getGetter('A'))..isReferencedAt('A);', true); |
| + assertThat(enumElement.getGetter('B'))..isReferencedAt('B);', true); |
| + } |
| + |
| + test_isReferencedBy_FieldElement_synthetic_hasGetter() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + A() : f = 42; |
| + int get f => 0; |
| +} |
| +'''); |
| + ClassElement element2 = findElement('A'); |
| + assertThat(element2.getField('f')).isWrittenAt('f = 42', true); |
| + } |
| + |
| + test_isReferencedBy_FieldElement_synthetic_hasGetterSetter() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + A() : f = 42; |
| + int get f => 0; |
| + set f(_) {} |
| +} |
| +'''); |
| + ClassElement element2 = findElement('A'); |
| + assertThat(element2.getField('f')).isWrittenAt('f = 42', true); |
| + } |
| + |
| + test_isReferencedBy_FieldElement_synthetic_hasSetter() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + A() : f = 42; |
| + set f(_) {} |
| +} |
| +'''); |
| + ClassElement element2 = findElement('A'); |
| + assertThat(element2.getField('f')).isWrittenAt('f = 42', true); |
| + } |
| + |
| + test_isReferencedBy_FunctionElement() async { |
| + await _indexTestUnit(''' |
| +foo() {} |
| +main() { |
| + print(foo); |
| + print(foo()); |
| +} |
| +'''); |
| + FunctionElement element = findElement('foo'); |
| + assertThat(element) |
| + ..isReferencedAt('foo);', false) |
| + ..isInvokedAt('foo());', false); |
| + } |
| + |
| + test_isReferencedBy_FunctionElement_with_LibraryElement() async { |
| + provider.newFile( |
| + _p('$testProject/foo.dart'), |
| + r''' |
| +bar() {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import "foo.dart"; |
| +main() { |
| + bar(); |
| +} |
| +'''); |
| + LibraryElement fooLibrary = testLibraryElement.imports[0].importedLibrary; |
| + assertThat(fooLibrary)..isReferencedAt('"foo.dart";', true, length: 10); |
| + { |
| + FunctionElement bar = fooLibrary.definingCompilationUnit.functions[0]; |
| + assertThat(bar)..isInvokedAt('bar();', false); |
| + } |
| + } |
| + |
| + test_isReferencedBy_FunctionTypeAliasElement() async { |
| + await _indexTestUnit(''' |
| +typedef A(); |
| +main(A p) { |
| +} |
| +'''); |
| + Element element = findElement('A'); |
| + assertThat(element)..isReferencedAt('A p) {', false); |
| + } |
| + |
| + /** |
| + * There was a bug in the AST structure, when single [Comment] was cloned and |
| + * assigned to both [FieldDeclaration] and [VariableDeclaration]. |
| + * |
| + * This caused duplicate indexing. |
| + * Here we test that the problem is fixed one way or another. |
| + */ |
| + test_isReferencedBy_identifierInComment() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +/// [A] text |
| +var myVariable = null; |
| +'''); |
| + Element element = findElement('A'); |
| + assertThat(element)..isReferencedAt('A] text', false); |
| + } |
| + |
| + test_isReferencedBy_MethodElement() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + method() {} |
| + main() { |
| + print(this.method); // q |
| + print(method); // nq |
| + } |
| +}'''); |
| + MethodElement element = findElement('method'); |
| + assertThat(element) |
| + ..isReferencedAt('method); // q', true) |
| + ..isReferencedAt('method); // nq', false); |
| + } |
| + |
| + test_isReferencedBy_ParameterElement() async { |
| + await _indexTestUnit(''' |
| +foo({var p}) {} |
| +main() { |
| + foo(p: 1); |
| +} |
| +'''); |
| + Element element = findElement('p'); |
| + assertThat(element)..isReferencedAt('p: 1', true); |
| + } |
| + |
| + test_isReferencedBy_TopLevelVariableElement() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +library lib; |
| +var V; |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' show V; // imp |
| +import 'lib.dart' as pref; |
| +main() { |
| + pref.V = 5; // q |
| + print(pref.V); // q |
| + V = 5; // nq |
| + print(V); // nq |
| +}'''); |
| + TopLevelVariableElement variable = importedUnit().topLevelVariables[0]; |
| + assertThat(variable)..isReferencedAt('V; // imp', true); |
| + assertThat(variable.getter) |
| + ..isReferencedAt('V); // q', true) |
| + ..isReferencedAt('V); // nq', false); |
| + assertThat(variable.setter) |
| + ..isReferencedAt('V = 5; // q', true) |
| + ..isReferencedAt('V = 5; // nq', false); |
| + } |
| + |
| + test_isReferencedBy_TopLevelVariableElement_synthetic_hasGetterSetter() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +int get V => 0; |
| +void set V(_) {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' show V; |
| +'''); |
| + TopLevelVariableElement element = importedUnit().topLevelVariables[0]; |
| + assertThat(element).isReferencedAt('V;', true); |
| + } |
| + |
| + test_isReferencedBy_TopLevelVariableElement_synthetic_hasSetter() async { |
| + provider.newFile( |
| + _p('$testProject/lib.dart'), |
| + ''' |
| +void set V(_) {} |
| +'''); |
| + await _indexTestUnit(''' |
| +import 'lib.dart' show V; |
| +'''); |
| + TopLevelVariableElement element = importedUnit().topLevelVariables[0]; |
| + assertThat(element).isReferencedAt('V;', true); |
| + } |
| + |
| + test_isReferencedBy_typeInVariableList() async { |
| + await _indexTestUnit(''' |
| +class A {} |
| +A myVariable = null; |
| +'''); |
| + Element element = findElement('A'); |
| + assertThat(element).isReferencedAt('A myVariable', false); |
| + } |
| + |
| + test_isWrittenBy_FieldElement() async { |
| + await _indexTestUnit(''' |
| +class A { |
| + int field; |
| + A.foo({this.field}); |
| + A.bar() : field = 5; |
| +} |
| +'''); |
| + FieldElement element = findElement('field', ElementKind.FIELD); |
| + assertThat(element) |
| + ..isWrittenAt('field})', true) |
| + ..isWrittenAt('field = 5', true); |
| + } |
| + |
| + test_usedName_inLibraryIdentifier() async { |
| + await _indexTestUnit(''' |
| +library aaa.bbb.ccc; |
| +class C { |
| + var bbb; |
| +} |
| +main(p) { |
| + p.bbb = 1; |
| +} |
| +'''); |
| + assertThatName('bbb') |
| + ..isNotUsed('bbb.ccc', IndexRelationKind.IS_READ_BY) |
| + ..isUsedQ('bbb = 1;', IndexRelationKind.IS_WRITTEN_BY); |
| + } |
| + |
| + test_usedName_qualified_resolved() async { |
| + await _indexTestUnit(''' |
| +class C { |
| + var x; |
| +} |
| +main(C c) { |
| + c.x; |
| + c.x = 1; |
| + c.x += 2; |
| + c.x(); |
| +} |
| +'''); |
| + assertThatName('x') |
| + ..isNotUsedQ('x;', IndexRelationKind.IS_READ_BY) |
| + ..isNotUsedQ('x = 1;', IndexRelationKind.IS_WRITTEN_BY) |
| + ..isNotUsedQ('x += 2;', IndexRelationKind.IS_READ_WRITTEN_BY) |
| + ..isNotUsedQ('x();', IndexRelationKind.IS_INVOKED_BY); |
| + } |
| + |
| + test_usedName_qualified_unresolved() async { |
| + await _indexTestUnit(''' |
| +main(p) { |
| + p.x; |
| + p.x = 1; |
| + p.x += 2; |
| + p.x(); |
| +} |
| +'''); |
| + assertThatName('x') |
| + ..isUsedQ('x;', IndexRelationKind.IS_READ_BY) |
| + ..isUsedQ('x = 1;', IndexRelationKind.IS_WRITTEN_BY) |
| + ..isUsedQ('x += 2;', IndexRelationKind.IS_READ_WRITTEN_BY) |
| + ..isUsedQ('x();', IndexRelationKind.IS_INVOKED_BY); |
| + } |
| + |
| + test_usedName_unqualified_resolved() async { |
| + await _indexTestUnit(''' |
| +class C { |
| + var x; |
| + m() { |
| + x; |
| + x = 1; |
| + x += 2; |
| + x(); |
| + } |
| +} |
| +'''); |
| + assertThatName('x') |
| + ..isNotUsedQ('x;', IndexRelationKind.IS_READ_BY) |
| + ..isNotUsedQ('x = 1;', IndexRelationKind.IS_WRITTEN_BY) |
| + ..isNotUsedQ('x += 2;', IndexRelationKind.IS_READ_WRITTEN_BY) |
| + ..isNotUsedQ('x();', IndexRelationKind.IS_INVOKED_BY); |
| + } |
| + |
| + test_usedName_unqualified_unresolved() async { |
| + await _indexTestUnit(''' |
| +main() { |
| + x; |
| + x = 1; |
| + x += 2; |
| + x(); |
| +} |
| +'''); |
| + assertThatName('x') |
| + ..isUsed('x;', IndexRelationKind.IS_READ_BY) |
| + ..isUsed('x = 1;', IndexRelationKind.IS_WRITTEN_BY) |
| + ..isUsed('x += 2;', IndexRelationKind.IS_READ_WRITTEN_BY) |
| + ..isUsed('x();', IndexRelationKind.IS_INVOKED_BY); |
| + } |
| + |
| + void _addTestFile(String content) { |
| + provider.newFile(testFile, content); |
| + driver.addFile(testFile); |
| + } |
| + |
| + /** |
| + * Asserts that [index] has an item with the expected properties. |
| + */ |
| + void _assertHasRelation( |
| + Element element, |
| + List<_Relation> relations, |
| + IndexRelationKind expectedRelationKind, |
| + ExpectedLocation expectedLocation) { |
| + for (_Relation relation in relations) { |
| + if (relation.kind == expectedRelationKind && |
| + relation.offset == expectedLocation.offset && |
| + relation.length == expectedLocation.length && |
| + relation.isQualified == expectedLocation.isQualified) { |
| + return; |
| + } |
| + } |
| + _failWithIndexDump( |
| + 'not found\n$element $expectedRelationKind at $expectedLocation'); |
| + } |
| + |
| + void _assertUsedName(String name, IndexRelationKind kind, |
| + ExpectedLocation expectedLocation, bool isNot) { |
| + int nameId = _getStringId(name); |
| + for (int i = 0; i < index.usedNames.length; i++) { |
| + if (index.usedNames[i] == nameId && |
| + index.usedNameKinds[i] == kind && |
| + index.usedNameOffsets[i] == expectedLocation.offset && |
| + index.usedNameIsQualifiedFlags[i] == expectedLocation.isQualified) { |
| + if (isNot) { |
| + _failWithIndexDump('Unexpected $name $kind at $expectedLocation'); |
| + } |
| + return; |
| + } |
| + } |
| + if (isNot) { |
| + return; |
| + } |
| + _failWithIndexDump('Not found $name $kind at $expectedLocation'); |
| + } |
| + |
| + ExpectedLocation _expectedLocation(String search, bool isQualified, |
| + {int length}) { |
| + int offset = findOffset(search); |
| + if (length == null) { |
| + length = getLeadingIdentifierLength(search); |
| + } |
| + return new ExpectedLocation(testUnitElement, offset, length, isQualified); |
| + } |
| + |
| + void _failWithIndexDump(String msg) { |
| + String packageIndexJsonString = |
| + new JsonEncoder.withIndent(' ').convert(index.toJson()); |
| + fail('$msg in\n' + packageIndexJsonString); |
| + } |
| + |
| + /** |
| + * Return the [element] identifier in [index] or fail. |
| + */ |
| + int _findElementId(Element element) { |
| + int unitId = _getUnitId(element); |
| + // Prepare the element that was put into the index. |
| + IndexElementInfo info = new IndexElementInfo(element); |
| + element = info.element; |
| + // Prepare element's name components. |
| + int unitMemberId = index.nullStringId; |
| + int classMemberId = index.nullStringId; |
| + int parameterId = index.nullStringId; |
| + for (Element e = element; e != null; e = e.enclosingElement) { |
| + if (e.enclosingElement is CompilationUnitElement) { |
| + unitMemberId = _getStringId(e.name); |
| + break; |
| + } |
| + } |
| + for (Element e = element; e != null; e = e.enclosingElement) { |
| + if (e.enclosingElement is ClassElement) { |
| + classMemberId = _getStringId(e.name); |
| + break; |
| + } |
| + } |
| + if (element is ParameterElement) { |
| + parameterId = _getStringId(element.name); |
| + } |
| + // Find the element's id. |
| + for (int elementId = 0; |
| + elementId < index.elementUnits.length; |
| + elementId++) { |
| + if (index.elementUnits[elementId] == unitId && |
| + index.elementNameUnitMemberIds[elementId] == unitMemberId && |
| + index.elementNameClassMemberIds[elementId] == classMemberId && |
| + index.elementNameParameterIds[elementId] == parameterId && |
| + index.elementKinds[elementId] == info.kind) { |
| + return elementId; |
| + } |
| + } |
| + _failWithIndexDump('Element $element is not referenced'); |
| + return 0; |
| + } |
| + |
| + /** |
| + * Return all relations with [element] in [index]. |
| + */ |
| + List<_Relation> _getElementRelations(Element element) { |
| + int elementId = _findElementId(element); |
| + List<_Relation> relations = <_Relation>[]; |
| + for (int i = 0; i < index.usedElementOffsets.length; i++) { |
| + if (index.usedElements[i] == elementId) { |
| + relations.add(new _Relation( |
| + index.usedElementKinds[i], |
| + index.usedElementOffsets[i], |
| + index.usedElementLengths[i], |
| + index.usedElementIsQualifiedFlags[i])); |
| + } |
| + } |
| + return relations; |
| + } |
| + |
| + int _getStringId(String str) { |
| + int id = index.strings.indexOf(str); |
| + if (id < 0) { |
| + _failWithIndexDump('String "$str" is not referenced'); |
| + } |
| + return id; |
| + } |
| + |
| + int _getUnitId(Element element) { |
| + CompilationUnitElement unitElement = getUnitElement(element); |
| + int libraryUriId = _getUriId(unitElement.library.source.uri); |
| + int unitUriId = _getUriId(unitElement.source.uri); |
| + expect(index.unitLibraryUris, hasLength(index.unitUnitUris.length)); |
| + for (int i = 0; i < index.unitLibraryUris.length; i++) { |
| + if (index.unitLibraryUris[i] == libraryUriId && |
| + index.unitUnitUris[i] == unitUriId) { |
| + return i; |
| + } |
| + } |
| + _failWithIndexDump('Unit $unitElement of $element is not referenced'); |
| + return -1; |
| + } |
| + |
| + int _getUriId(Uri uri) { |
| + String str = uri.toString(); |
| + return _getStringId(str); |
| + } |
| + |
| + Future<Null> _indexTestUnit(String code) async { |
| + testCode = code; |
| + _addTestFile(testCode); |
| + |
| + AnalysisResult result = await driver.getResult(testFile); |
| + testUnit = result.unit; |
| + testUnitElement = testUnit.element; |
| + testLibraryElement = testUnitElement.library; |
| + |
| + AnalysisDriverUnitIndexBuilder indexBuilder = indexUnit(testUnit); |
| + List<int> indexBytes = indexBuilder.toBuffer(); |
| + index = new AnalysisDriverUnitIndex.fromBuffer(indexBytes); |
| + } |
| + |
| + String _p(String path) => provider.convertPath(path); |
| +} |
| + |
| +class _ElementIndexAssert { |
| + final IndexTest test; |
| + final Element element; |
| + final List<_Relation> relations; |
| + |
| + _ElementIndexAssert(this.test, this.element, this.relations); |
| + |
| + void hasRelationCount(int expectedCount) { |
| + expect(relations, hasLength(expectedCount)); |
| + } |
| + |
| + void isAncestorOf(String search, {int length}) { |
| + test._assertHasRelation( |
| + element, |
| + relations, |
| + IndexRelationKind.IS_ANCESTOR_OF, |
| + test._expectedLocation(search, false, length: length)); |
| + } |
| + |
| + void isExtendedAt(String search, bool isQualified, {int length}) { |
| + test._assertHasRelation( |
| + element, |
| + relations, |
| + IndexRelationKind.IS_EXTENDED_BY, |
| + test._expectedLocation(search, isQualified, length: length)); |
| + } |
| + |
| + void isImplementedAt(String search, bool isQualified, {int length}) { |
| + test._assertHasRelation( |
| + element, |
| + relations, |
| + IndexRelationKind.IS_IMPLEMENTED_BY, |
| + test._expectedLocation(search, isQualified, length: length)); |
| + } |
| + |
| + void isInvokedAt(String search, bool isQualified, {int length}) { |
| + test._assertHasRelation(element, relations, IndexRelationKind.IS_INVOKED_BY, |
| + test._expectedLocation(search, isQualified, length: length)); |
| + } |
| + |
| + void isMixedInAt(String search, bool isQualified, {int length}) { |
| + test._assertHasRelation( |
| + element, |
| + relations, |
| + IndexRelationKind.IS_MIXED_IN_BY, |
| + test._expectedLocation(search, isQualified, length: length)); |
| + } |
| + |
| + void isReferencedAt(String search, bool isQualified, {int length}) { |
| + test._assertHasRelation( |
| + element, |
| + relations, |
| + IndexRelationKind.IS_REFERENCED_BY, |
| + test._expectedLocation(search, isQualified, length: length)); |
| + } |
| + |
| + void isWrittenAt(String search, bool isQualified, {int length}) { |
| + test._assertHasRelation(element, relations, IndexRelationKind.IS_WRITTEN_BY, |
| + test._expectedLocation(search, isQualified, length: length)); |
| + } |
| +} |
| + |
| +/** |
| + * Wraps an [_ElementVisitorFunction] into a [GeneralizingElementVisitor]. |
| + */ |
| +class _ElementVisitorFunctionWrapper extends GeneralizingElementVisitor { |
| + final _ElementVisitorFunction function; |
| + |
| + _ElementVisitorFunctionWrapper(this.function); |
| + |
| + visitElement(Element element) { |
| + function(element); |
| + super.visitElement(element); |
| + } |
| +} |
| + |
| +class _NameIndexAssert { |
| + final IndexTest test; |
| + final String name; |
| + |
| + _NameIndexAssert(this.test, this.name); |
| + |
| + void isNotUsed(String search, IndexRelationKind kind) { |
| + test._assertUsedName( |
| + name, kind, test._expectedLocation(search, false), true); |
| + } |
| + |
| + void isNotUsedQ(String search, IndexRelationKind kind) { |
| + test._assertUsedName( |
| + name, kind, test._expectedLocation(search, true), true); |
| + } |
| + |
| + void isUsed(String search, IndexRelationKind kind) { |
| + test._assertUsedName( |
| + name, kind, test._expectedLocation(search, false), false); |
| + } |
| + |
| + void isUsedQ(String search, IndexRelationKind kind) { |
| + test._assertUsedName( |
| + name, kind, test._expectedLocation(search, true), false); |
| + } |
| +} |
| + |
| +class _Relation { |
| + final IndexRelationKind kind; |
| + final int offset; |
| + final int length; |
| + final bool isQualified; |
| + |
| + _Relation(this.kind, this.offset, this.length, this.isQualified); |
| + |
| + @override |
| + String toString() { |
| + return '_Relation{kind: $kind, offset: $offset, length: $length, ' |
| + 'isQualified: $isQualified}lified)'; |
| + } |
| +} |