blob: 1972496c7c6b654c8988bd6ee60bc334c85ba0ac [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../analysis_abstract.dart';
import '../analysis_server_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(AnalysisNotificationOccurrencesTest);
});
}
@reflectiveTest
class AnalysisNotificationOccurrencesTest extends PubPackageAnalysisServerTest {
late List<Occurrences> occurrencesList;
late Occurrences testOccurrences;
final Completer<void> _resultsAvailable = Completer();
@override
List<String> get experiments => [
...super.experiments,
EnableString.patterns,
EnableString.records,
];
/// Asserts that there is an offset of [search] in [testOccurrences].
void assertHasOffset(String search) {
var offset = findOffset(search);
expect(testOccurrences.offsets, contains(offset));
}
/// Validates that there is a region at the offset of [search] in [testFile].
/// If [length] is not specified explicitly, then length of an identifier
/// from [search] is used.
void assertHasRegion(String search, [int length = -1]) {
var offset = findOffset(search);
if (length == -1) {
length = findIdentifierLength(search);
}
findRegion(offset, length, true);
}
Future<void> assertOccurrences(
String content, {
required ElementKind? kind,
}) async {
final code = TestCode.parse(content);
addTestFile(code.code);
await prepareOccurrences();
// Find the result from the first range
final range = code.ranges.first;
final sourceRange = range.sourceRange;
findRegion(sourceRange.offset, sourceRange.length, true);
expect(testOccurrences.element.kind, kind);
expect(testOccurrences.element.name, range.text);
expect(testOccurrences.offsets,
containsAll(code.ranges.map((r) => r.sourceRange.offset)));
}
/// Finds an [Occurrences] with the given [offset] and [length].
///
/// If [exists] is `true`, then fails if such [Occurrences] does not exist.
/// Otherwise remembers this it into [testOccurrences].
///
/// If [exists] is `false`, then fails if such [Occurrences] exists.
void findRegion(int offset, int length, [bool? exists]) {
for (var occurrences in occurrencesList) {
if (occurrences.length != length) {
continue;
}
for (var occurrenceOffset in occurrences.offsets) {
if (occurrenceOffset == offset) {
if (exists == false) {
fail('Not expected to find (offset=$offset; length=$length) in\n'
'${occurrencesList.join('\n')}');
}
testOccurrences = occurrences;
return;
}
}
}
if (exists == true) {
fail('Expected to find (offset=$offset; length=$length) in\n'
'${occurrencesList.join('\n')}');
}
}
Future<void> prepareOccurrences() async {
await addAnalysisSubscription(AnalysisService.OCCURRENCES, testFile);
return _resultsAvailable.future;
}
@override
void processNotification(Notification notification) {
if (notification.event == ANALYSIS_NOTIFICATION_OCCURRENCES) {
var params = AnalysisOccurrencesParams.fromNotification(notification);
if (params.file == testFile.path) {
occurrencesList = params.occurrences;
_resultsAvailable.complete();
}
}
}
@override
Future<void> setUp() async {
super.setUp();
await setRoots(included: [workspaceRootPath], excluded: []);
}
Future<void> test_afterAnalysis() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
void f() {
var /*[0*/vvv/*0]*/ = 42;
print(/*[1*/vvv/*1]*/);
}
''',
);
}
Future<void> test_enum() async {
await assertOccurrences(
kind: ElementKind.ENUM,
'''
enum /*[0*/E/*0]*/ {
v;
}
void f(/*[1*/E/*1]*/ e) {
/*[2*/E/*2]*/.v;
}
''',
);
}
Future<void> test_enum_constant() async {
await assertOccurrences(
kind: ElementKind.ENUM_CONSTANT,
'''
enum E {
/*[0*/v/*0]*/;
}
void f() {
E./*[1*/v/*1]*/;
}
''',
);
}
Future<void> test_enum_field() async {
await assertOccurrences(
kind: ElementKind.FIELD,
'''
enum E {
v;
final int /*[0*/foo/*0]*/ = 0;
}
void f(E e) {
e./*[1*/foo/*1]*/;
}
''',
);
}
Future<void> test_enum_getter() async {
await assertOccurrences(
kind: ElementKind.FIELD,
'''
enum E {
v;
int get /*[0*/foo/*0]*/ => 0;
}
void f(E e) {
e./*[1*/foo/*1]*/;
}
''',
);
}
Future<void> test_enum_method() async {
await assertOccurrences(
kind: ElementKind.METHOD,
'''
enum E {
v;
void /*[0*/foo/*0]*/() {}
}
void f(E e) {
e./*[1*/foo/*1]*/();
}
''',
);
}
Future<void> test_enum_setter() async {
await assertOccurrences(
kind: ElementKind.FIELD,
'''
enum E {
v;
set /*[0*/foo/*0]*/(int _) {}
}
void f(E e) {
e./*[1*/foo/*1]*/ = 0;
}
''',
);
}
Future<void> test_field() async {
await assertOccurrences(
kind: ElementKind.FIELD,
'''
class A {
int /*[0*/fff/*0]*/;
A(this./*[1*/fff/*1]*/);
void f() {
/*[2*/fff/*2]*/ = 42;
print(/*[3*/fff/*3]*/);
}
}
''',
);
}
Future<void> test_field_unresolved() async {
addTestFile('''
class A {
A(this.noSuchField);
}
''');
// no checks for occurrences, just ensure that there is no NPE
await prepareOccurrences();
}
Future<void> test_localVariable() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
void f() {
var /*[0*/vvv/*0]*/ = 42;
/*[1*/vvv/*1]*/ += 5;
print(/*[2*/vvv/*2]*/);
}
''',
);
}
Future<void> test_memberField() async {
await assertOccurrences(
kind: ElementKind.FIELD,
'''
class A<T> {
T /*[0*/fff/*0]*/;
}
void f() {
var a = new A<int>();
var b = new A<String>();
a./*[1*/fff/*1]*/ = 1;
b./*[2*/fff/*2]*/ = 2;
}
''',
);
}
Future<void> test_memberMethod() async {
await assertOccurrences(
kind: ElementKind.METHOD,
'''
class A<T> {
T /*[0*/mmm/*0]*/() {}
}
void f() {
var a = new A<int>();
var b = new A<String>();
a./*[1*/mmm/*1]*/();
b./*[2*/mmm/*2]*/();
}
''',
);
}
Future<void> test_mixin() async {
await assertOccurrences(
kind: ElementKind.MIXIN,
'''
mixin /*[0*/A/*0]*/ {
void aaa() {}
}
class B with /*[1*/A/*1]*/ {}
''',
);
}
Future<void> test_parameter_named1() async {
await assertOccurrences(
kind: ElementKind.PARAMETER,
'''
void f(int aaa, int bbb, {int? /*[0*/ccc/*0]*/, int? ddd}) {
/*[1*/ccc/*1]*/;
ddd;
}
void g() {
f(0, /*[2*/ccc/*2]*/: 2, 1, ddd: 3);
}
''',
);
}
Future<void> test_parameter_named2() async {
await assertOccurrences(
kind: ElementKind.PARAMETER,
'''
void f(int aaa, int bbb, {int? ccc, int? /*[0*/ddd/*0]*/}) {
ccc;
/*[1*/ddd/*1]*/;
}
void g() {
f(0, ccc: 2, 1, /*[2*/ddd/*2]*/: 3);
}
''',
);
}
Future<void> test_pattern_assignment() async {
await assertOccurrences(
kind: ElementKind.PARAMETER,
'''
void f(String /*[0*/a/*0]*/, String b) {
(b, /*[1*/a/*1]*/) = (/*[2*/a/*2]*/, b);
}
''',
);
}
Future<void> test_pattern_assignment_list() async {
await assertOccurrences(
kind: ElementKind.PARAMETER,
'''
void f(List<int> x, num /*[0*/a/*0]*/) {
[/*[1*/a/*1]*/] = x;
}
);
''',
);
}
Future<void> test_pattern_cast_typeName() async {
await assertOccurrences(
kind: ElementKind.CLASS,
'''
String f((num, /*[0*/String/*0]*/) record) {
var (i as int, s as /*[1*/String/*1]*/) = record;
}
);
''',
);
}
Future<void> test_pattern_cast_variable() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
void f((num, String) record) {
var (i as int, /*[0*/s/*0]*/ as String) = record;
print(/*[1*/s/*1]*/);
}
);
''',
);
}
Future<void> test_pattern_map() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
void f(x) {
switch (x) {
case {0: String /*[0*/a/*0]*/}:
print(/*[1*/a/*1]*/);
break;
}
}
);
''',
);
}
Future<void> test_pattern_map_typeArguments() async {
await assertOccurrences(
kind: ElementKind.CLASS,
'''
/*[0*/String/*0]*/ f(x) {
switch (x) {
case <int, /*[1*/String/*1]*/>{0: var a}:
return a;
break;
}
}
);
''',
);
}
Future<void> test_pattern_nullAssert() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
void f((int?, int?) position) {
var (x!, /*[0*/y/*0]*/!) = position;
print(/*[1*/y/*1]*/);
}
);
''',
);
}
Future<void> test_pattern_nullCheck() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
void f(String? maybeString) {
switch (maybeString) {
case var /*[0*/s/*0]*/?:
print(/*[1*/s/*1]*/);
}
}
);
''',
);
}
Future<void> test_pattern_object_fieldName() async {
await assertOccurrences(
kind: ElementKind.FIELD,
'''
double calculateArea(Shape shape) =>
switch (shape) {
Square(/*[0*/length/*0]*/: var l) => l * l,
};
class Shape { }
class Square extends Shape {
double get /*[1*/length/*1]*/ => 0;
}
);
''',
);
}
Future<void> test_pattern_object_typeName() async {
await assertOccurrences(
kind: ElementKind.CLASS,
'''
double calculateArea(Shape shape) =>
switch (shape) {
/*[0*/Square/*0]*/(length: var l) => l * l,
};
class Shape { }
class /*[1*/Square/*1]*/ extends Shape {
double get length => 0;
}
);
''',
);
}
Future<void> test_pattern_object_variable() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
double calculateArea(Shape shape) =>
switch (shape) {
Square(length: var /*[0*/l/*0]*/) => /*[1*/l/*1]*/ * /*[2*/l/*2]*/,
};
class Shape { }
class Square extends Shape {
double get length => 0;
}
);
''',
);
}
Future<void> test_pattern_record_variable() async {
await assertOccurrences(
kind: ElementKind.PARAMETER,
'''
void f(({int foo}) x, num /*[0*/a/*0]*/) {
(foo: /*[1*/a,/*1]*/) = x;
}
);
''',
);
}
Future<void> test_pattern_relational_variable() async {
await assertOccurrences(
kind: ElementKind.LOCAL_VARIABLE,
'''
String f(int char) {
const /*[0*/zero/*0]*/ = 0;
return switch (char) {
== /*[1*/zero/*1]*/ => 'zero'
};
}
);
''',
);
}
Future<void> test_recordType_typeName() async {
await assertOccurrences(
kind: ElementKind.CLASS,
r'''
/*[0*/double/*0]*/ f((/*[1*/double/*1]*/, /*[2*/double/*2]*/) param) {
}
);
''',
);
}
Future<void> test_superFormalParameter_requiredPositional() async {
await assertOccurrences(
kind: ElementKind.PARAMETER,
'''
class A {
A(int x);
}
class B extends A {
int y;
B(super./*[0*/x/*0]*/) : y = /*[1*/x/*1]*/ * 2;
}
''',
);
}
Future<void> test_topLevelVariable() async {
await assertOccurrences(
kind: ElementKind.TOP_LEVEL_VARIABLE,
'''
var /*[0*/VVV/*0]*/ = 1;
void f() {
/*[1*/VVV/*1]*/ = 2;
print(/*[2*/VVV/*2]*/);
}
''',
);
}
Future<void> test_type_class() async {
await assertOccurrences(
kind: ElementKind.CLASS,
'''
void f() {
/*[0*/int/*0]*/ a = 1;
/*[1*/int/*1]*/ b = 2;
/*[2*/int/*2]*/ c = 3;
}
/*[3*/int/*3]*/ VVV = 4;
''',
);
}
Future<void> test_type_class_definition() async {
await assertOccurrences(
kind: ElementKind.CLASS,
'''
class /*[0*/A/*0]*/ {}
/*[1*/A/*1]*/ a;
''',
);
}
Future<void> test_type_dynamic() async {
addTestFile('''
void f() {
dynamic a = 1;
dynamic b = 2;
}
dynamic V = 3;
''');
await prepareOccurrences();
var offset = findOffset('dynamic a');
findRegion(offset, 'dynamic'.length, false);
}
Future<void> test_type_void() async {
addTestFile('''
void f() {
}
''');
await prepareOccurrences();
var offset = findOffset('void f()');
findRegion(offset, 'void'.length, false);
}
}