| // Copyright (c) 2019, 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 'package:analysis_server/lsp_protocol/protocol.dart'; | |
| import 'package:analysis_server/src/lsp/constants.dart'; | |
| import 'package:analyzer/src/test_utilities/test_code_format.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; | |
| import '../tool/lsp_spec/matchers.dart'; | |
| import '../utils/test_code_extensions.dart'; | |
| import 'server_abstract.dart'; | |
| void main() { | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(DocumentHighlightsTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class DocumentHighlightsTest extends AbstractLspAnalysisServerTest { | |
| Future<void> test_bound_topLevelVariable_wildcard() async { | |
| await _testMarkedContent( | |
| ''' | |
| var /*[0*/_/*0]*/ = 1; | |
| void f() { | |
| var _ = 2; | |
| print(/*[1*/_/*1]*/); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_catch_error() async { | |
| await _testMarkedContent( | |
| r''' | |
| void foo() { | |
| try {} catch (/*[0*/error/*0]*/, stackTrace) { | |
| print('$/*[1*/error/*1]*/, $stackTrace'); | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_catch_stack() async { | |
| await _testMarkedContent( | |
| r''' | |
| void foo() { | |
| try {} catch (error, /*[0*/stackTrace/*0]*/) { | |
| print('$error, $/*[1*/stackTrace/*1]*/'); | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_class_field_underscore() async { | |
| await _testMarkedContent( | |
| ''' | |
| class C { | |
| int /*[0*/_/*0]*/ = 0; | |
| } | |
| void f(int _) { | |
| int _ = 1; | |
| C()./*[1*/_/*1]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_class_getterSetter() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| int /*[0*/a/*0]*/ = 1; | |
| } | |
| class B extends A { | |
| int get /*[1*/a/*1]*/ => 1; | |
| set /*[2*/a/*2]*/(int _) {} | |
| } | |
| void f(A a, B b) { | |
| a./*[3*/a/*3]*/ = b./*[4*/a/*4]*/; | |
| b./*[5*/a/*5]*/ = a./*[6*/a/*6]*/; | |
| } | |
| ''', | |
| kinds: { | |
| 0: .Write, | |
| 1: .Write, | |
| 2: .Write, | |
| 3: .Write, | |
| 4: .Read, | |
| 5: .Write, | |
| 6: .Read, | |
| }, | |
| ); | |
| } | |
| Future<void> test_constructor_inBody_className() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa { | |
| /*[0*/Aaa/*0]*/(); | |
| } | |
| void f() { | |
| Aaa a = Aaa./*[1*/new/*1]*/(); | |
| var b = /*[2*/Aaa/*2]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| /// The factory keyword is considered the constructor when there's no | |
| /// constructor name. | |
| Future<void> test_constructor_inBody_factory() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa { | |
| new _(); | |
| /*[0*/factory/*0]*/() => Aaa._(); | |
| } | |
| void f() { | |
| Aaa a = Aaa./*[1*/new/*1]*/(); | |
| var b = /*[2*/Aaa/*2]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| /// No matches for factory keyword when there is a constructor name. | |
| Future<void> | |
| test_constructor_inBody_factory_withoutType_named_keyword() async { | |
| await _testMarkedContent(''' | |
| class Aaa { | |
| new _(); | |
| fact^ory named() => Aaa._(); | |
| } | |
| void f() { | |
| Aaa a = Aaa.named(); | |
| } | |
| '''); | |
| } | |
| /// Names match names. | |
| Future<void> test_constructor_inBody_factory_withoutType_named_name() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa { | |
| new _(); | |
| factory /*[0*/named/*0]*/() => Aaa._(); | |
| } | |
| void f() { | |
| Aaa a = Aaa./*[1*/named/*1]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| /// No matches for factory keyword when there is a type name because the type | |
| /// name is considered the constructor. | |
| Future<void> test_constructor_inBody_factory_withType_keyword() async { | |
| await _testMarkedContent(''' | |
| class Aaa { | |
| new _(); | |
| fact^ory Aaa() => Aaa._(); | |
| } | |
| void f() { | |
| Aaa a = Aaa(); | |
| } | |
| '''); | |
| } | |
| /// Type names match type names for invocation/declaration. | |
| Future<void> test_constructor_inBody_factory_withType_typeName() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa { | |
| new _(); | |
| factory /*[0*/Aaa/*0]*/() => Aaa._(); | |
| } | |
| void f() { | |
| Aaa a = /*[1*/Aaa/*1]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_constructor_inBody_new() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa { | |
| /*[0*/new/*0]*/(); | |
| } | |
| void f() { | |
| Aaa a = Aaa./*[1*/new/*1]*/(); | |
| var b = /*[2*/Aaa/*2]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| /// No matches for new keyword when there is a constructor name. | |
| Future<void> test_constructor_inBody_new_withoutType_named_keyword() async { | |
| await _testMarkedContent(''' | |
| class Aaa { | |
| n^ew named(); | |
| } | |
| void f() { | |
| Aaa a = Aaa.named(); | |
| } | |
| '''); | |
| } | |
| /// When `new` is used as the constructor identifier, it's a match for | |
| /// constructor invocations. | |
| Future<void> test_constructor_inBody_new_withType_keyword() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa { | |
| Aaa./*[0*/new/*0]*/(); | |
| } | |
| void f() { | |
| Aaa a = /*[1*/Aaa/*1]*/(); | |
| Aaa b = Aaa./*[2*/new/*2]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| /// When `new` is used as the constructor identifier, the type name is a | |
| /// match for the type name. | |
| Future<void> test_constructor_inBody_new_withType_typeName() async { | |
| await _testMarkedContent( | |
| ''' | |
| class /*[0*/Aaa/*0]*/ { | |
| /*[1*/Aaa/*1]*/.new(); | |
| } | |
| void f() { | |
| /*[2*/Aaa/*2]*/ a = Aaa(); | |
| /*[3*/Aaa/*3]*/ b = /*[4*/Aaa/*4]*/.new(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read, 3: .Read, 4: .Read}, | |
| ); | |
| } | |
| Future<void> test_constructor_inBody_newNamed() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa { | |
| new /*[0*/named/*0]*/(); | |
| } | |
| void f() { | |
| Aaa value = Aaa./*[1*/named/*1]*/(); | |
| Aaa./*[2*/named/*2]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_dartCode_issue5369_field() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| var /*[0*/a/*0]*/ = [''].where((_) => true).toList(); | |
| List<String> f() { | |
| return /*[1*/a/*1]*/; | |
| } | |
| } | |
| var a; // Not a reference | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_dartCode_issue5369_functionType() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| String m({ required String Function(String input) /*[0*/f/*0]*/ }) { | |
| return /*[1*/f/*1]*/(''); | |
| } | |
| } | |
| var f; // Not a reference | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_dartCode_issue5369_localVariable() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| List<String> f() { | |
| var /*[0*/a/*0]*/ = [''].where((_) => true).toList(); | |
| return /*[1*/a/*1]*/; | |
| } | |
| } | |
| var a; // Not a reference | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_dartCode_issue5369_topLevelVariable() async { | |
| await _testMarkedContent( | |
| ''' | |
| var /*[0*/a/*0]*/ = [''].where((_) => true).toList(); | |
| var b = /*[1*/a/*1]*/; | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_dotShorthand_class() async { | |
| await _testMarkedContent( | |
| ''' | |
| A topA = ./*[0*/a/*0]*/; | |
| class A { | |
| static A get /*[1*/a/*1]*/ => A(); | |
| } | |
| void fn(A a) => print(a); | |
| void f() { | |
| A a = ./*[2*/a/*2]*/; | |
| fn(./*[3*/a/*3]*/); | |
| A aa = A./*[4*/a/*4]*/; | |
| } | |
| ''', | |
| kinds: {0: .Read, 1: .Write, 2: .Read, 3: .Read, 4: .Read}, | |
| ); | |
| } | |
| Future<void> test_dotShorthand_enum() async { | |
| await _testMarkedContent( | |
| ''' | |
| const A constA = ./*[0*/a/*0]*/; | |
| enum A { /*[1*/a/*1]*/ } | |
| void fn(A a) => print(a); | |
| void f() { | |
| A a = ./*[2*/a/*2]*/; | |
| fn(./*[3*/a/*3]*/); | |
| A aa = A./*[4*/a/*4]*/; | |
| } | |
| ''', | |
| kinds: {0: .Read, 1: .Write, 2: .Read, 3: .Read, 4: .Read}, | |
| ); | |
| } | |
| Future<void> test_dotShorthand_extensionType() async { | |
| await _testMarkedContent( | |
| ''' | |
| A topA = ./*[0*/a/*0]*/; | |
| extension type A(int x) { | |
| static A get /*[1*/a/*1]*/ => A(1); | |
| } | |
| void fn(A a) => print(a); | |
| void f() { | |
| A a = ./*[2*/a/*2]*/; | |
| fn(./*[3*/a/*3]*/); | |
| A aa = A./*[4*/a/*4]*/; | |
| } | |
| ''', | |
| kinds: {0: .Read, 1: .Write, 2: .Read, 3: .Read, 4: .Read}, | |
| ); | |
| } | |
| Future<void> test_enum() async { | |
| await _testMarkedContent( | |
| ''' | |
| enum /*[0*/E/*0]*/ { | |
| v; | |
| } | |
| void f(/*[1*/E/*1]*/ e) { | |
| /*[2*/E/*2]*/.v; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_enum_constant() async { | |
| await _testMarkedContent( | |
| ''' | |
| enum E { | |
| /*[0*/v/*0]*/; | |
| } | |
| void f() { | |
| E./*[1*/v/*1]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_enum_field() async { | |
| await _testMarkedContent( | |
| ''' | |
| enum E { | |
| v; | |
| final int /*[0*/foo/*0]*/ = 0; | |
| } | |
| void f(E e) { | |
| e./*[1*/foo/*1]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_enum_getter() async { | |
| await _testMarkedContent( | |
| ''' | |
| enum E { | |
| v; | |
| int get /*[0*/foo/*0]*/ => 0; | |
| } | |
| void f(E e) { | |
| e./*[1*/foo/*1]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_enum_method() async { | |
| await _testMarkedContent( | |
| ''' | |
| enum E { | |
| v; | |
| void /*[0*/foo/*0]*/() {} | |
| } | |
| void f(E e) { | |
| e./*[1*/foo/*1]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_enum_setter() async { | |
| await _testMarkedContent(''' | |
| enum E { | |
| v; | |
| set /*[0*/foo/*0]*/(int _) {} | |
| } | |
| void f(E e) { | |
| e./*[1*/foo/*1]*/ = 0; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_extension() async { | |
| await _testMarkedContent(''' | |
| void foo(int i) { | |
| /*[0*/E/*0]*/(i).self; | |
| } | |
| extension /*[1*/E/*1]*/<ThisType> on ThisType { | |
| ThisType get self => this; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_extensionMember() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension on int { | |
| int get /*[0*/foo/*0]*/ => 0; | |
| } | |
| void f(int v) { | |
| v./*[1*/foo/*1]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionMember_differentiation() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension on String { | |
| int get foo => 0; | |
| } | |
| extension on int { | |
| int get /*[0*/foo/*0]*/ => 0; | |
| } | |
| void f(int v, String s) { | |
| v./*[1*/foo/*1]*/; | |
| s.foo; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionType() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension type /*[0*/E/*0]*/(int it) {} | |
| void f(/*[1*/E/*1]*/ e) {} | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionType_constructor_inBody() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension type E(int it) { | |
| E./*[0*/named/*0]*/() : this(0); | |
| } | |
| void f() { | |
| E./*[1*/named/*1]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionType_constructor_primary() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension type E./*[0*/named/*0]*/(int it) {} | |
| void f() { | |
| E./*[1*/named/*1]*/(0); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionType_getter() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension type E(int it) { | |
| int get /*[0*/foo/*0]*/ => 0; | |
| } | |
| void f(E e) { | |
| e./*[1*/foo/*1]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionType_method() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension type E(int it) { | |
| void /*[0*/foo/*0]*/() {} | |
| } | |
| void f(E e) { | |
| e./*[1*/foo/*1]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionType_parameter() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension type E(int /*[0*/it/*0]*/) { | |
| int get value => /*[1*/it/*1]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_extensionType_setter() async { | |
| await _testMarkedContent(''' | |
| extension type E(int it) { | |
| set /*[0*/foo/*0]*/(int _) {} | |
| } | |
| void f(E e) { | |
| e./*[1*/foo/*1]*/ = 0; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_field() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| int /*[0*/fff/*0]*/; | |
| A(this./*[1*/fff/*1]*/); | |
| void f() { | |
| /*[2*/fff/*2]*/ = 42; | |
| print(/*[3*/fff/*3]*/); | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Write, 3: .Read}, | |
| ); | |
| } | |
| Future<void> test_field_unresolved() async { | |
| failTestOnErrorDiagnostic = false; | |
| // Unresolved fields should not return any highlights (or error). | |
| await _testMarkedContent(''' | |
| class A { | |
| A(this.noSuc^hField); | |
| } | |
| '''); | |
| } | |
| Future<void> test_forInLoop() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f() { | |
| for (final /*[0*/x/*0]*/ in []) { | |
| /*[1*/x/*1]*/; | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_formalParameters_closure() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(void Function(int) _) {} | |
| void g() => f((/*[0*/variable/*0]*/) { | |
| print(/*[1*/variable/*1]*/); | |
| }); | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_formalParameters_function() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(int /*[0*/parameter/*0]*/) { | |
| print(/*[1*/parameter/*1]*/); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_formalParameters_method() async { | |
| await _testMarkedContent( | |
| ''' | |
| class C { | |
| void m(int /*[0*/parameter/*0]*/) { | |
| print(/*[1*/parameter/*1]*/); | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_functions() async { | |
| await _testMarkedContent( | |
| ''' | |
| /*[0*/main/*0]*/() { | |
| /*[1*/main/*1]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_extends() async { | |
| await _testMarkedContent(''' | |
| abstract class A { | |
| void /*[0*/a/*0]*/(); | |
| } | |
| class B extends A { | |
| @override | |
| void /*[1*/a/*1]*/() {} | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_hierarchy_method_extends_implements_fromLeaf() async { | |
| await _testMarkedContent( | |
| ''' | |
| abstract class A { | |
| void /*[0*/a/*0]*/(); | |
| } | |
| abstract class I { | |
| void /*[1*/a/*1]*/(); // Included because we started at B.a | |
| } | |
| class B extends A implements I { | |
| @override | |
| void /*[2*/^a/*2]*/() {} | |
| } | |
| void f(A a, I i, B b) { | |
| a./*[3*/a/*3]*/(); | |
| i./*[4*/a/*4]*/(); | |
| b./*[5*/a/*5]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Write, 3: .Read, 4: .Read, 5: .Read}, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_extends_implements_fromRoot() async { | |
| await _testMarkedContent( | |
| ''' | |
| abstract class A { | |
| void /*[0*/^a/*0]*/(); | |
| } | |
| abstract class I { | |
| void a(); // Not included because we started at A.a | |
| } | |
| class B extends A implements I { | |
| @override | |
| void /*[1*/a/*1]*/() {} | |
| } | |
| void f(A a, I i, B b) { | |
| a./*[2*/a/*2]*/(); | |
| i.a(); | |
| b./*[3*/a/*3]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read, 3: .Read}, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_extends_with_fromLeaf() async { | |
| await _testMarkedContent( | |
| ''' | |
| abstract class A { | |
| void /*[0*/a/*0]*/(); | |
| } | |
| mixin M { | |
| void /*[1*/a/*1]*/() {} // Included because we started at B.a | |
| } | |
| class B extends A with M { | |
| @override | |
| void /*[2*/^a/*2]*/() {} | |
| } | |
| void f(A a, M m, B b) { | |
| a./*[3*/a/*3]*/(); | |
| m./*[4*/a/*4]*/(); | |
| b./*[5*/a/*5]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Write, 3: .Read, 4: .Read, 5: .Read}, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_extends_with_fromRoot() async { | |
| await _testMarkedContent( | |
| ''' | |
| abstract class A { | |
| void /*[0*/a^/*0]*/(); | |
| } | |
| mixin M { | |
| void a() {} // Not included because we started at A.a | |
| } | |
| class B extends A with M { | |
| @override | |
| void /*[1*/a/*1]*/() {} | |
| } | |
| void f(A a, M m, B b) { | |
| a./*[2*/a/*2]*/(); | |
| m.a(); | |
| b./*[3*/a/*3]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read, 3: .Read}, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_extends_with_implements_fromLeaf() async { | |
| await _testMarkedContent( | |
| ''' | |
| abstract class A { | |
| void /*[0*/a/*0]*/(); | |
| } | |
| mixin M { | |
| void /*[1*/a/*1]*/() {} // Included because we started from A.a | |
| } | |
| abstract class I { | |
| void /*[2*/a/*2]*/(); // Included because we started from B.a | |
| } | |
| class B extends A with M implements I { | |
| @override | |
| void /*[3*/^a/*3]*/() {} | |
| } | |
| void f(A a, M m, I i, B b) { | |
| a./*[4*/a/*4]*/(); | |
| m./*[5*/a/*5]*/(); | |
| i./*[6*/a/*6]*/(); | |
| b./*[7*/a/*7]*/(); | |
| } | |
| ''', | |
| kinds: { | |
| 0: .Write, | |
| 1: .Write, | |
| 2: .Write, | |
| 3: .Write, | |
| 4: .Read, | |
| 5: .Read, | |
| 6: .Read, | |
| 7: .Read, | |
| }, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_extends_with_implements_fromRoot() async { | |
| await _testMarkedContent( | |
| ''' | |
| abstract class A { | |
| void /*[0*/^a/*0]*/(); | |
| } | |
| mixin M { | |
| void a() {} // Not included because we started from A.a | |
| } | |
| abstract class I { | |
| void a(); // Not included because we started from A.a | |
| } | |
| class B extends A with M implements I { | |
| @override | |
| void /*[1*/a/*1]*/() {} | |
| } | |
| void f(A a, M m, I i, B b) { | |
| a./*[2*/a/*2]*/(); | |
| m.a(); | |
| i.a(); | |
| b./*[3*/a/*3]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read, 3: .Read}, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_implements() async { | |
| await _testMarkedContent(''' | |
| abstract class A { | |
| void /*[0*/a/*0]*/(); | |
| } | |
| class B implements A { | |
| @override | |
| void /*[1*/a/*1]*/() {} | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_hierarchy_method_with() async { | |
| await _testMarkedContent(''' | |
| mixin M { | |
| void /*[0*/a/*0]*/() {} | |
| } | |
| class B with M { | |
| @override | |
| void /*[1*/a/*1]*/() {} | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_hierarchy_method_with_implements_fromLeaf() async { | |
| await _testMarkedContent( | |
| ''' | |
| mixin M { | |
| void /*[0*/a/*0]*/() {} // Included because we started at B.a | |
| } | |
| abstract class I { | |
| void /*[1*/a/*1]*/(); // Included because we started at B.a | |
| } | |
| class B with M implements I { | |
| @override | |
| void /*[2*/^a/*2]*/() {} | |
| } | |
| void f(M m, I i, B b) { | |
| m./*[3*/a/*3]*/(); | |
| i./*[4*/a/*4]*/(); | |
| b./*[5*/a/*5]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Write, 3: .Read, 4: .Read, 5: .Read}, | |
| ); | |
| } | |
| Future<void> test_hierarchy_method_with_implements_fromRoot() async { | |
| await _testMarkedContent( | |
| ''' | |
| mixin M { | |
| void /*[0*/^a/*0]*/() {} | |
| } | |
| abstract class I { | |
| void a(); // Not included because we started at M.a | |
| } | |
| class B with M implements I { | |
| @override | |
| void /*[1*/a/*1]*/() {} | |
| } | |
| void f(M m, I i, B b) { | |
| m./*[2*/a/*2]*/(); | |
| i.a(); | |
| b./*[3*/a/*3]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read, 3: .Read}, | |
| ); | |
| } | |
| Future<void> test_invalidLineByOne() async { | |
| // Test that requesting a line that's too high by one returns a valid | |
| // error response instead of throwing. | |
| const content = '// single line'; | |
| await initialize(); | |
| await openFile(mainFileUri, content); | |
| // Lines are zero-based so 1 is invalid. | |
| var pos = Position(line: 1, character: 0); | |
| var request = getDocumentHighlights(mainFileUri, pos); | |
| await expectLater( | |
| request, | |
| throwsA(isResponseError(ServerErrorCodes.invalidFileLineCol)), | |
| ); | |
| } | |
| Future<void> test_keyword_loop_do() => _testLoop('do', '', 'while (true);'); | |
| Future<void> test_keyword_loop_for() => _testLoop('for', '(;;)', ''); | |
| Future<void> test_keyword_loop_while() => _testLoop('while', '(true)', ''); | |
| Future<void> test_keyword_loopWithSwitch_loopExit() async { | |
| await _testMarkedContent(''' | |
| void f(int i) { | |
| /*[0*/for/*0]*/ (;;) { | |
| /*[1*/break/*1]*/; | |
| /*[2*/continue/*2]*/; | |
| switch (i) { | |
| case 1: | |
| break; | |
| /*[3*/continue/*3]*/; | |
| case 2: | |
| break; | |
| /*[4*/continue/*4]*/; | |
| } | |
| } | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_loopWithSwitch_switchExit() async { | |
| await _testMarkedContent(''' | |
| void f(int i) { | |
| for (;;) { | |
| break; | |
| continue; | |
| /*[0*/switch/*0]*/ (i) { | |
| case 1: | |
| /*[1*/break/*1]*/; | |
| continue; | |
| case 2: | |
| /*[2*/break/*2]*/; | |
| continue; | |
| } | |
| } | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_return_function() async { | |
| await _testMarkedContent(''' | |
| int f() { | |
| if (true) /*[0*/return/*0]*/ 1; | |
| /*[1*/return/*1]*/ 2; | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_return_insideLoop() async { | |
| await _testMarkedContent(''' | |
| int f(int i) { | |
| for (;;) { | |
| switch (i) { | |
| case 1: | |
| /*[0*/return/*0]*/ 1; | |
| case 2: | |
| /*[1*/return/*1]*/ 2; | |
| break; | |
| continue; | |
| } | |
| } | |
| /*[2*/return/*2]*/ 2; | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_return_method() async { | |
| await _testMarkedContent(''' | |
| class C { | |
| int m() { | |
| if (true) /*[0*/return/*0]*/ 1; | |
| /*[1*/return/*1]*/ 2; | |
| } | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_return_nestedClosure() async { | |
| await _testMarkedContent(''' | |
| int outerFunction() { | |
| var a = () { | |
| var b = () { | |
| var c = () { | |
| return 1; | |
| }; | |
| if (true) /*[0*/return/*0]*/ 1; | |
| /*[1*/return/*1]*/ 0; | |
| }; | |
| return 1; | |
| }; | |
| return 1; | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_return_nestedFunction() async { | |
| await _testMarkedContent(''' | |
| int outerFunction() { | |
| int middleFunction() { | |
| int innerFunction() { | |
| return 1; | |
| } | |
| if (true) /*[0*/return/*0]*/ 1; | |
| /*[1*/return/*1]*/ 2; | |
| } | |
| return 1; | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_yield_asyncGenerator() async { | |
| await _testMarkedContent(''' | |
| Stream<int> outerFunction() async* { | |
| Stream<int> middleFunction() async* { | |
| Stream<int> innerFunction() async* { | |
| yield 1; | |
| yield* Stream.value(0); | |
| } | |
| if (true) /*[0*/yield/*0]*/ 1; | |
| if (true) /*[1*/yield/*1]*/* Stream.value(0); | |
| /*[2*/yield/*2]*/ 2; | |
| /*[3*/yield/*3]*/* Stream.value(0); | |
| } | |
| yield 1; | |
| yield* Stream.value(0); | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_keyword_yield_syncGenerator() async { | |
| await _testMarkedContent(''' | |
| Iterable<int> outerFunction() sync* { | |
| Iterable<int> middleFunction() sync* { | |
| Iterable<int> innerFunction() sync* { | |
| yield 1; | |
| yield* Iterable.empty(); | |
| } | |
| if (true) /*[0*/yield/*0]*/ 1; | |
| if (true) /*[1*/yield/*1]*/* Iterable.empty(); | |
| /*[2*/yield/*2]*/ 2; | |
| /*[3*/yield/*3]*/* Iterable.empty(); | |
| } | |
| yield 1; | |
| yield* Iterable.empty(); | |
| } | |
| ''', allOfKind: .Text); | |
| } | |
| Future<void> test_localVariable() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f() { | |
| var /*[0*/foo/*0]*/ = 1; | |
| print(/*[1*/foo/*1]*/); | |
| /*[2*/foo/*2]*/ = 2; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Write}, | |
| ); | |
| } | |
| Future<void> test_member_field() async { | |
| await _testMarkedContent(''' | |
| class A<T> { | |
| T /*[0*/fff/*0]*/; | |
| A(this./*[1*/fff/*1]*/); | |
| } | |
| void f() { | |
| var a = A<int>(1); | |
| var b = A<String>(''); | |
| a./*[2*/fff/*2]*/ = 1; | |
| b./*[3*/fff/*3]*/ = ''; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_member_method() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A<T> { | |
| T /*[0*/mmm/*0]*/() => throw 0; | |
| } | |
| void f() { | |
| var a = A<int>(); | |
| var b = A<String>(); | |
| a./*[1*/mmm/*1]*/(); | |
| b./*[2*/mmm/*2]*/(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_method_underscore() async { | |
| await _testMarkedContent( | |
| ''' | |
| class C { | |
| /*[0*/_/*0]*/() { | |
| /*[1*/_/*1]*/(); | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_mixin() async { | |
| await _testMarkedContent( | |
| ''' | |
| mixin /*[0*/A/*0]*/ { | |
| void aaa() {} | |
| } | |
| class B with /*[1*/A/*1]*/ {} | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_nonDartFile() async { | |
| await initialize(); | |
| await openFile(pubspecFileUri, simplePubspecContent); | |
| var highlights = await getDocumentHighlights(pubspecFileUri, startOfDocPos); | |
| // Non-Dart files should return empty results, not errors. | |
| expect(highlights, isEmpty); | |
| } | |
| Future<void> test_noResult() async { | |
| await _testMarkedContent(''' | |
| void f() { | |
| // This one is in a ^ comment! | |
| } | |
| '''); | |
| } | |
| Future<void> test_onlySelf() async { | |
| await _testMarkedContent(''' | |
| void f() { | |
| /*[0*/print/*0]*/(''); | |
| } | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_onlySelf_wildcard() async { | |
| await _testMarkedContent(''' | |
| void f() { | |
| var /*[0*/_/*0]*/ = ''; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_parameter_named() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(int aaa, int bbb, {int? /*[0*/ccc/*0]*/, int? ddd}) { | |
| /*[1*/ccc/*1]*/; | |
| ddd; | |
| } | |
| void g() { | |
| f(0, 1, /*[2*/ccc/*2]*/: 2, ddd: 3); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Write}, | |
| ); | |
| } | |
| Future<void> test_parameter_privateNamed() async { | |
| await _testMarkedContent(''' | |
| class C { | |
| int? /*[0*/_aaa/*0]*/; | |
| C({this./*[1*/_aaa^/*1]*/}); | |
| } | |
| void f() { | |
| C(/*[2*/aaa/*2]*/: 123); | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_parameter_privateNamed_argument() async { | |
| await _testMarkedContent(''' | |
| class C { | |
| int? _aaa; | |
| C({this./*[0*/_aaa/*0]*/}); | |
| } | |
| void f() { | |
| C(/*[1*/aaa^/*1]*/: 123); | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_parameter_privateNamed_field() async { | |
| await _testMarkedContent(''' | |
| class C { | |
| int? /*[0*/_aaa^/*0]*/; | |
| C({this./*[1*/_aaa/*1]*/}); | |
| } | |
| void f() { | |
| C(aaa: 123); | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_parameter_wildcard() async { | |
| await _testMarkedContent(''' | |
| void f(int /*[0*/_/*0]*/) {} | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_part_export_show() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'class A {}'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a.dart'; | |
| export 'a.dart' show /*[0*/A/*0]*/; | |
| void f(/*[1*/A/*1]*/ a) {} | |
| ''', | |
| filePath: partFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_part_import_hide() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'class A {} class B {}'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a.dart' hide /*[0*/A/*0]*/; | |
| import 'a.dart' as a; | |
| void f(a./*[1*/A/*1]*/ a1) {} | |
| ''', | |
| filePath: partFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_part_import_nested() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'class A {}'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| newFile(partFilePath, ''' | |
| part of 'main.dart'; | |
| part 'subpart.dart'; | |
| '''); | |
| var subpartFilePath = join(projectFolderPath, 'lib', 'subpart.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'part.dart'; | |
| import 'a.dart' show /*[0*/A/*0]*/; | |
| void f(/*[1*/A/*1]*/ a) {} | |
| ''', | |
| filePath: subpartFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_part_import_prefix() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'class A {}'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a.dart' as /*[0*/p/*0]*/; | |
| void f() { | |
| /*[1*/p/*1]*/.A(); | |
| } | |
| ''', | |
| filePath: partFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_part_import_shadow() async { | |
| var a1FilePath = join(projectFolderPath, 'lib', 'a1.dart'); | |
| var a2FilePath = join(projectFolderPath, 'lib', 'a2.dart'); | |
| newFile(a1FilePath, 'class A {}'); | |
| newFile(a2FilePath, 'class A {}'); | |
| newFile(mainFilePath, ''' | |
| import 'a1.dart'; | |
| part 'part.dart'; | |
| '''); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a2.dart' show /*[0*/A/*0]*/; | |
| void f(/*[1*/A/*1]*/ a) {} | |
| ''', | |
| filePath: partFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_part_import_show() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'class A {}'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a.dart' show /*[0*/A/*0]*/; | |
| void f(/*[1*/A/*1]*/ a) { | |
| /*[2*/A/*2]*/? a2; | |
| } | |
| ''', | |
| filePath: partFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_part_import_show_extension() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'extension Ext on int { void foo() {} }'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a.dart' show /*[0*/Ext/*0]*/; | |
| void f() { | |
| /*[1*/Ext/*1]*/(1).foo(); | |
| } | |
| ''', | |
| filePath: partFilePath, | |
| kinds: {0: .Read, 1: .Write}, | |
| ); | |
| } | |
| Future<void> test_part_import_show_multiple() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'class A {} class B {}'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a.dart' show /*[0*/A/*0]*/, B; | |
| void f(/*[1*/A/*1]*/ a) {} | |
| ''', | |
| filePath: partFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_part_import_show_typeAlias() async { | |
| var aFilePath = join(projectFolderPath, 'lib', 'a.dart'); | |
| newFile(aFilePath, 'typedef IntList = List<int>;'); | |
| newFile(mainFilePath, "part 'part.dart';"); | |
| var partFilePath = join(projectFolderPath, 'lib', 'part.dart'); | |
| await _testMarkedContent( | |
| ''' | |
| part of 'main.dart'; | |
| import 'a.dart' show /*[0*/IntList/*0]*/; | |
| void f(/*[1*/IntList/*1]*/ list) {} | |
| ''', | |
| filePath: partFilePath, | |
| allOfKind: .Read, | |
| ); | |
| } | |
| Future<void> test_pattern_assignment() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(String /*[0*/a/*0]*/, String b) { | |
| (b, /*[1*/a/*1]*/) = (/*[2*/a/*2]*/, b); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_assignment_list() async { | |
| await _testMarkedContent(''' | |
| void f(List<int> x, num /*[0*/a/*0]*/) { | |
| [/*[1*/a/*1]*/] = x; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_pattern_cast_typeName() async { | |
| await _testMarkedContent(''' | |
| /*[0*/String/*0]*/ f((num, /*[1*/String/*1]*/) record) { | |
| var (i as int, s as /*[2*/String/*2]*/) = record; | |
| return s; | |
| } | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_pattern_cast_variable() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f((num, String) record) { | |
| var (i as int, /*[0*/s/*0]*/ as String) = record; | |
| print(/*[1*/s/*1]*/); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_map() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(x) { | |
| switch (x) { | |
| case {0: String /*[0*/a/*0]*/}: | |
| print(/*[1*/a/*1]*/); | |
| break; | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_map_typeArguments() async { | |
| await _testMarkedContent(''' | |
| /*[0*/String/*0]*/ f(x) { | |
| switch (x) { | |
| case <int, /*[1*/String/*1]*/>{0: var a}: | |
| return a; | |
| break; | |
| } | |
| return ''; | |
| } | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_pattern_nullAssert() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f((int?, int?) position) { | |
| var (x!, /*[0*/y/*0]*/!) = position; | |
| print(/*[1*/y/*1]*/); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_nullCheck() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(String? maybeString) { | |
| switch (maybeString) { | |
| case var /*[0*/s/*0]*/?: | |
| print(/*[1*/s/*1]*/); | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_object_destructure() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f() { | |
| final MapEntry(:/*[0*/key/*0]*/) = const MapEntry<String, int>('a', 1); | |
| if (const MapEntry('a', 1) case MapEntry(:final /*[1*/ke^y/*1]*/)) { | |
| /*[2*/key/*2]*/; | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_object_destructure_getter() async { | |
| await _testMarkedContent(''' | |
| class A { | |
| String? /*[0*/key/*0]*/; | |
| } | |
| void f() { | |
| final A(:/*[1*/key/*1]*/) = A(); | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_pattern_object_destructure_variable() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| String? /*[0*/key/*0]*/; | |
| } | |
| void f() { | |
| final A(:/*[1*/k^ey/*1]*/) = A(); | |
| /*[2*/key/*2]*/; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_object_fieldName() async { | |
| await _testMarkedContent(''' | |
| double calculateArea(Shape shape) => | |
| switch (shape) { | |
| Square(/*[0*/length/*0]*/: var l) => l * l, | |
| Shape() => 0.0, | |
| }; | |
| class Shape { } | |
| class Square extends Shape { | |
| double get /*[1*/length/*1]*/ => 0; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_pattern_object_typeName() async { | |
| await _testMarkedContent( | |
| ''' | |
| double calculateArea(Shape shape) => | |
| switch (shape) { | |
| /*[0*/Square/*0]*/(length: var l) => l * l, | |
| Shape() => 0.0, | |
| }; | |
| class Shape { } | |
| class /*[1*/Square/*1]*/ extends Shape { | |
| double get length => 0; | |
| } | |
| ''', | |
| kinds: {0: .Read, 1: .Write}, | |
| ); | |
| } | |
| Future<void> test_pattern_object_variable() async { | |
| await _testMarkedContent( | |
| ''' | |
| double calculateArea(Shape shape) => | |
| switch (shape) { | |
| Square(length: var /*[0*/l/*0]*/) => /*[1*/l/*1]*/ * /*[2*/l/*2]*/, | |
| Shape() => 0.0, | |
| }; | |
| class Shape { } | |
| class Square extends Shape { | |
| double get length => 0; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_pattern_record_variable() async { | |
| await _testMarkedContent(''' | |
| void f(({int foo}) x, num /*[0*/a/*0]*/) { | |
| (foo: /*[1*/a/*1]*/,) = x; | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| Future<void> test_pattern_relational_variable() async { | |
| await _testMarkedContent( | |
| ''' | |
| String f(int char) { | |
| const /*[0*/zero/*0]*/ = 0; | |
| return switch (char) { | |
| == /*[1*/zero/*1]*/ => 'zero', | |
| _ => '', | |
| }; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_patternVariable_ifCase_logicalOr() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(Object? x) { | |
| if (x case int /*[0*/test/*0]*/ || [int /*[1*/test/*1]*/] when /*[2*/test/*2]*/ > 0) { | |
| /*[3*/test/*3]*/; | |
| /*[4*/test/*4]*/ = 1; | |
| /*[5*/test/*5]*/ += 2; | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Write, 2: .Read, 3: .Read, 4: .Write, 5: .Write}, | |
| ); | |
| } | |
| Future<void> test_prefix() async { | |
| await _testMarkedContent(''' | |
| import '' as /*[0*/p/*0]*/; | |
| class A { | |
| void m() { | |
| /*[1*/p/*1]*/.foo(); | |
| print(/*[2*/p/*2]*/.a); | |
| /*[3*/p/*3]*/.A(); | |
| } | |
| } | |
| void foo() {} | |
| /*[4*/p/*4]*/.A? a; | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_prefix_null() async { | |
| failTestOnErrorDiagnostic = false; | |
| // Note, we use `core` just to have some prefix. | |
| // The actual check is no crash on unresolved `prefix`. | |
| await _testMarkedContent(''' | |
| import 'dart:core' as /*[0*/core/*0]*/; | |
| void f(prefix.A? _, /*[1*/core/*1]*/.int _) {} | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_prefix_wildcard() async { | |
| // Ensure no crash. | |
| await _testMarkedContent(''' | |
| import 'dart:io' as /*[0*/_/*0]*/; | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_prefixed() async { | |
| await _testMarkedContent( | |
| ''' | |
| import '' as p; | |
| class /*[0*/A/*0]*/ {} | |
| p./*[1*/A/*1]*/? a; | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| /// The body/this keyword isn't an occurrence of anything. | |
| Future<void> test_primaryConstructor_body() async { | |
| await _testMarkedContent(''' | |
| class Aaa() { | |
| th^is {} | |
| } | |
| Aaa a = Aaa(); | |
| '''); | |
| } | |
| Future<void> test_primaryConstructor_declaringParameter_declaration() async { | |
| await _testMarkedContent( | |
| ''' | |
| /// This references [/*[0*/i/*0]*/]. | |
| class C({var int? /*[1*/i^/*1]*/}); | |
| void f(int? parameter) { | |
| var c = C(/*[2*/i/*2]*/: parameter); | |
| C(/*[3*/i/*3]*/: c./*[4*/i/*4]*/); | |
| } | |
| ''', | |
| kinds: {0: .Read, 1: .Write, 2: .Write, 3: .Write, 4: .Read}, | |
| ); | |
| } | |
| Future<void> | |
| test_primaryConstructor_declaringParameter_private_declaration() async { | |
| await _testMarkedContent( | |
| r''' | |
| /// This references [/*[0*/_i/*0]*/]. | |
| class C({var int? /*[1*/_i^/*1]*/}) { | |
| String toString() => '${/*[2*/_i/*2]*/}'; | |
| } | |
| void f(int? parameter) { | |
| var _ = C(/*[3*/i/*3]*/: parameter); | |
| } | |
| ''', | |
| kinds: {0: .Read, 1: .Write, 2: .Read, 3: .Write}, | |
| ); | |
| } | |
| Future<void> test_primaryConstructor_declaringPrivateParameter_field() async { | |
| await _testMarkedContent( | |
| r''' | |
| /// This references [/*[0*/_i^/*0]*/]. | |
| class C({var int? /*[1*/_i/*1]*/}) { | |
| String toString() => '${/*[2*/_i/*2]*/}'; | |
| } | |
| void f(int? parameter) { | |
| var _ = C(i: parameter); | |
| } | |
| ''', | |
| kinds: {0: .Read, 1: .Write, 2: .Read}, | |
| ); | |
| } | |
| Future<void> | |
| test_primaryConstructor_declaringPrivateParameter_parameter() async { | |
| await _testMarkedContent(r''' | |
| /// This references [_i]. | |
| class C({var int? /*[0*/_i/*0]*/}) { | |
| String toString() => '${_i}'; | |
| } | |
| void f(int? parameter) { | |
| var _ = C(/*[1*/i^/*1]*/: parameter); | |
| } | |
| ''', allOfKind: .Write); | |
| } | |
| /// The body/this keyword isn't an occurrence of anything. | |
| Future<void> test_primaryConstructor_named_body() async { | |
| await _testMarkedContent(''' | |
| class Aaa.named() { | |
| th^is {} | |
| } | |
| Aaa a = Aaa.named(); | |
| '''); | |
| } | |
| Future<void> test_primaryConstructor_named_constructorName() async { | |
| await _testMarkedContent( | |
| ''' | |
| class Aaa./*[0*/named/*0]*/() { | |
| this {} | |
| } | |
| Aaa a = Aaa./*[1*/named/*1]*/(); | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_primaryConstructor_named_typeName() async { | |
| await _testMarkedContent( | |
| ''' | |
| class /*[0*/Aaa/*0]*/.named() { | |
| this {} | |
| } | |
| /*[1*/Aaa/*1]*/ a = /*[2*/Aaa/*2]*/.named(); | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_primaryConstructor_typeName() async { | |
| await _testMarkedContent( | |
| ''' | |
| class /*[0*/Aaa/*0]*/() { | |
| this {} | |
| } | |
| /*[1*/Aaa/*1]*/ a = Aaa(); | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_primaryConstructor_typeName_constructorInvocation() async { | |
| await _testMarkedContent(''' | |
| class Aaa() { | |
| this {} | |
| } | |
| Aaa a = /*[0*/Aaa/*0]*/(); | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_recordType_typeName() async { | |
| await _testMarkedContent(''' | |
| /*[0*/double/*0]*/ f((/*[1*/double/*1]*/, /*[2*/double/*2]*/) param) { | |
| return 0.0; | |
| } | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_shadow_inner() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f() { | |
| var foo = 1; | |
| func() { | |
| var /*[0*/foo/*0]*/ = 2; | |
| print(/*[1*/foo/*1]*/); | |
| } | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_shadow_outer() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f() { | |
| var /*[0*/foo/*0]*/ = 1; | |
| func() { | |
| var foo = 2; | |
| print(foo); | |
| } | |
| print(/*[1*/foo/*1]*/); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_superFormalParameter_requiredPositional() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| A(int x); | |
| } | |
| class B extends A { | |
| int y; | |
| B(super./*[0*/x/*0]*/) : y = /*[1*/x/*1]*/ * 2; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_topLevelVariable() async { | |
| await _testMarkedContent( | |
| ''' | |
| String /*[0*/foo/*0]*/ = 'bar'; | |
| void f() { | |
| print(/*[1*/foo/*1]*/); | |
| /*[2*/foo/*2]*/ = ''; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Write}, | |
| ); | |
| } | |
| Future<void> test_topLevelVariable_underscore() async { | |
| await _testMarkedContent( | |
| ''' | |
| String /*[0*/_/*0]*/ = 'bar'; | |
| void f(int _) { | |
| int _ = 1; | |
| print(/*[1*/_/*1]*/); | |
| /*[2*/_/*2]*/ = ''; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Write}, | |
| ); | |
| } | |
| Future<void> test_type_class() async { | |
| await _testMarkedContent(''' | |
| void f() { | |
| /*[0*/int/*0]*/ a = 1; | |
| /*[1*/int/*1]*/ b = 2; | |
| /*[2*/int/*2]*/ c = 3; | |
| } | |
| /*[3*/int/*3]*/ VVV = 4; | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_type_class_constructors() async { | |
| await _testMarkedContent( | |
| ''' | |
| class /*[0*/A/*0]*/ { | |
| A(); // Default constructor is own entity | |
| /*[1*/A/*1]*/.named(); | |
| } | |
| /*[2*/A/*2]*/ a = A(); // Default constructor is own entity | |
| var b = /*[3*/A/*3]*/.new(); | |
| var c = /*[4*/A/*4]*/.new; | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read, 3: .Read, 4: .Read}, | |
| ); | |
| } | |
| Future<void> test_type_class_constructors2() async { | |
| await _testMarkedContent( | |
| ''' | |
| class /*[0*/A/*0]*/ { | |
| new(); // Default constructor is own entity | |
| /*[1*/A/*1]*/.named(); | |
| } | |
| /*[2*/A/*2]*/ a = A(); // Default constructor is own entity | |
| var b = /*[3*/A/*3]*/.new(); | |
| var c = /*[4*/A/*4]*/.new; | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read, 3: .Read, 4: .Read}, | |
| ); | |
| } | |
| /// The type name in default constructors are their own entity and not | |
| /// part of the type name. | |
| Future<void> test_type_class_constructors_default() async { | |
| await _testMarkedContent( | |
| ''' | |
| class A { | |
| /*[0*/A/*0]*/(); | |
| A.named(); | |
| } | |
| A a = /*[1*/A/*1]*/(); | |
| var b = A./*[2*/new/*2]*/(); | |
| var c = A./*[3*/new/*3]*/; | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read, 3: .Read}, | |
| ); | |
| } | |
| Future<void> test_type_class_definition() async { | |
| await _testMarkedContent( | |
| ''' | |
| class /*[0*/A/*0]*/ {} | |
| /*[1*/A/*1]*/? a; | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_type_dynamic() async { | |
| await _testMarkedContent(''' | |
| void f() { | |
| /*[0*/dynamic/*0]*/ a = 1; | |
| /*[1*/dynamic/*1]*/ b = 2; | |
| } | |
| /*[2*/dynamic/*2]*/ V = 3; | |
| ''', allOfKind: .Read); | |
| } | |
| Future<void> test_type_void() async { | |
| await _testMarkedContent(''' | |
| vo^id f() {} | |
| '''); | |
| } | |
| Future<void> test_typeAlias_class() async { | |
| await _testMarkedContent( | |
| ''' | |
| class MyClass {} | |
| mixin MyMixin {} | |
| class /*[0*/MyAlias/*0]*/ = MyClass with MyMixin; | |
| /*[1*/MyAlias/*1]*/? a; | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeAlias_function() async { | |
| await _testMarkedContent( | |
| ''' | |
| typedef /*[0*/myFunc/*0]*/(); | |
| /*[1*/myFunc/*1]*/? f; | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeAlias_generic() async { | |
| await _testMarkedContent( | |
| ''' | |
| typedef /*[0*/TD/*0]*/ = String; | |
| /*[1*/TD/*1]*/? a; | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_class() async { | |
| await _testMarkedContent( | |
| ''' | |
| abstract class A</*[0*/ThisType/*0]*/> { | |
| /*[1*/ThisType/*1]*/ f(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_enum() async { | |
| await _testMarkedContent( | |
| ''' | |
| enum E</*[0*/ThisType/*0]*/> { | |
| a; | |
| /*[1*/ThisType/*1]*/ get t => throw Error(); | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_extension() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension E</*[0*/ThisType/*0]*/> on /*[1*/ThisType/*1]*/ { | |
| /*[2*/ThisType/*2]*/ f() => this; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_extensionType() async { | |
| await _testMarkedContent( | |
| ''' | |
| extension type Et</*[0*/ThisType/*0]*/>(/*[1*/ThisType/*1]*/ value) { | |
| /*[2*/ThisType/*2]*/ get v => value; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_function() async { | |
| await _testMarkedContent( | |
| ''' | |
| /*[0*/ThisType/*0]*/ f</*[1*/ThisType/*1]*/>() => 0 as /*[2*/ThisType/*2]*/; | |
| ''', | |
| kinds: {0: .Read, 1: .Write, 2: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_functionParameter() async { | |
| await _testMarkedContent( | |
| ''' | |
| void f(/*[0*/ThisType/*0]*/ Function</*[1*/ThisType/*1]*/>() f) => f(); | |
| ''', | |
| kinds: {0: .Read, 1: .Write}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_mixin() async { | |
| await _testMarkedContent( | |
| ''' | |
| mixin M</*[0*/ThisType/*0]*/> { | |
| /*[1*/ThisType/*1]*/ get t; | |
| } | |
| ''', | |
| kinds: {0: .Write, 1: .Read}, | |
| ); | |
| } | |
| Future<void> test_typeParameter_typedef() async { | |
| await _testMarkedContent( | |
| ''' | |
| typedef TypeDef</*[0*/ThisType/*0]*/> = /*[1*/ThisType/*1]*/ Function(/*[2*/ThisType/*2]*/); | |
| ''', | |
| kinds: {0: .Write, 1: .Read, 2: .Read}, | |
| ); | |
| } | |
| /// Create three nested loops for this [loopKeyword] (outer/middle/inner) | |
| /// with all combinations of `break`/`continue` (and with.without labels) | |
| /// and verify that the middle [loopKeyword] and all exit keywords that | |
| /// relate to that loop produce mutual ranges including each other. | |
| Future<void> _testLoop( | |
| String loopKeyword, | |
| String loopStart, | |
| String loopEnd, | |
| ) async { | |
| var content = | |
| ''' | |
| void f() { | |
| outer: | |
| $loopKeyword $loopStart { | |
| middle: | |
| /*[0*/$loopKeyword/*0]*/ $loopStart { | |
| inner: | |
| $loopKeyword $loopStart { | |
| break; | |
| continue; | |
| break inner; | |
| continue inner; | |
| /*[1*/break/*1]*/ middle; | |
| /*[2*/continue/*2]*/ middle; | |
| break outer; | |
| continue outer; | |
| } $loopEnd | |
| /*[3*/break/*3]*/; | |
| /*[4*/continue/*4]*/; | |
| /*[5*/break/*5]*/ middle; | |
| /*[6*/continue/*6]*/ middle; | |
| break outer; | |
| continue outer; | |
| } $loopEnd | |
| break; | |
| continue; | |
| break outer; | |
| continue outer; | |
| } $loopEnd | |
| } | |
| '''; | |
| await _testMarkedContent(content, allOfKind: .Text); | |
| } | |
| /// Tests highlights in a Dart file using the provided content. | |
| /// | |
| /// The content should be marked up using the [TestCode] format. | |
| /// | |
| /// If the content contains positions, they will be used to fetch highlights | |
| /// and the resulting ranges verified. | |
| /// | |
| /// If the content contains only ranges, then the start and end of every range | |
| /// will be tested to ensure the full set of ranges are returned mutually for | |
| /// each. | |
| /// | |
| /// If [allOfKind] is provided, each returned highlight must have the same | |
| /// [DocumentHighlightKind]. Otherwise, the kinds will be checked against | |
| /// [kinds]. | |
| Future<void> _testMarkedContent( | |
| String content, { | |
| String? filePath, | |
| DocumentHighlightKind? allOfKind, | |
| Map<int, DocumentHighlightKind>? kinds, | |
| }) async { | |
| var code = TestCode.parse(content); | |
| expect( | |
| code.positions.isNotEmpty || code.ranges.isNotEmpty, | |
| isTrue, | |
| reason: 'At least one position or range must be marked in the content', | |
| ); | |
| if (code.ranges.isEmpty) { | |
| expect( | |
| allOfKind == null && kinds == null, | |
| isTrue, | |
| reason: | |
| "'allOfKind' and 'kinds' cannot be provided if there are no ranges", | |
| ); | |
| } else { | |
| expect( | |
| (allOfKind != null) != (kinds != null), | |
| isTrue, | |
| reason: "Exactly one of 'allOfKind' or 'kinds' must be provided if there are ranges", | |
| ); | |
| if (kinds != null && kinds.isNotEmpty) { | |
| expect( | |
| kinds.values.toSet(), | |
| hasLength(greaterThan(1)), | |
| reason: "Use 'allOfKind' if all kinds are the same", | |
| ); | |
| } | |
| } | |
| var fileUri = filePath != null ? toUri(filePath) : mainFileUri; | |
| newFile(filePath ?? mainFilePath, code.code); | |
| await initialize(); | |
| await openFile(fileUri, code.code); | |
| var positions = code.positions.isNotEmpty | |
| ? code.positions.map((position) => position.position) | |
| : code.ranges.expand((range) => [range.range.start, range.range.end]); | |
| for (var position in positions) { | |
| var highlights = (await getDocumentHighlights(fileUri, position))!; | |
| if (code.ranges.isEmpty) { | |
| expect(highlights, isEmpty); | |
| } else { | |
| code.verifyRanges(highlights.map((highlight) => highlight.range)); | |
| } | |
| // Also verify the kinds | |
| for (var i = 0; i < highlights.length; i++) { | |
| if (kinds != null && !kinds.containsKey(i)) { | |
| fail("Range $i was not found in 'kinds'"); | |
| } | |
| var expectedKind = allOfKind ?? kinds![i]; | |
| expect(highlights[i].kind, expectedKind); | |
| } | |
| } | |
| } | |
| } |