blob: b6c275e830b58e3a8ad5c14b27037796fdfd868f [file] [log] [blame]
// Copyright (c) 2025, 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:analyzer/src/test_utilities/test_code_format.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../utils/test_code_extensions.dart';
import 'server_abstract.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(InlineValueTest);
});
}
@reflectiveTest
class InlineValueTest extends AbstractLspAnalysisServerTest {
late TestCode code;
/// Whether to enable the inlineValuesProperties experiment flag in the
/// client configuration passed during initialization.
bool experimentalInlineValuesProperties = false;
Future<void> test_block_ifStatement_inside() async {
code = TestCode.parse(r'''
void f(int a, int b, int c) {
if (a == 1) {
/*[0*/a/*0]*/;
/*[1*/b/*1]*/;
/*[2*/c/*2]*/;
^
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_block_ifStatement_notInside() async {
code = TestCode.parse(r'''
void f(int a, int /*[0*/b/*0]*/, int c) {
if (/*[1*/a/*1]*/ == 1) {
// Code inside blocks is excluded
a;
b;
c;
}
^/*[2*/c/*2]*/;
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_block_switchStatement() async {
code = TestCode.parse(r'''
void f(int a, int /*[0*/b/*0]*/, int c) {
switch (/*[1*/a/*1]*/) {
case 0:
// Ignored because not in this case
a;
b;
c;
case 1:
^/*[2*/c/*2]*/;
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_iterables() async {
experimentalInlineValuesProperties = true;
// There are no marked ranges, because none of these should produce values.
code = TestCode.parse(r'''
import 'dart:async';
void f(
Iterable<int> p1,
Future<int> p2,
FutureOr<int> p3,
Stream<int> p4,
) {
^
}
''');
await verify_values(code);
}
Future<void> test_parameter_declaration() async {
code = TestCode.parse(r'''
void f(int /*[0*/aaa/*0]*/, int /*[1*/bbb/*1]*/) {
^
aaa + bbb;
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
/// Lists are included, iterables are not.
Future<void> test_parameter_iterables() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
void f(List list1, List<int> /*[0*/list2/*0]*/, Iterable iterable1, Iterable iterable2) {
print(/*[1*/list1/*1]*/);
print(iterable1);
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_parameter_nestedInFunctionType() async {
// A parameter nested inside a function type should not produce a value.
code = TestCode.parse(r'''
void f(
int /*[0*/value/*0]*/, {
required void Function(int value) /*[1*/func/*1]*/,
}) {
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_parameter_read() async {
code = TestCode.parse(r'''
void f(int aaa, int bbb) {
^/*[0*/aaa/*0]*/ + /*[1*/bbb/*1]*/;
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_parameter_write() async {
code = TestCode.parse(r'''
void f(int aaa, int bbb, int ccc) {
/*[0*/aaa/*0]*/++;
/*[1*/bbb/*1]*/ = 1;
/*[2*/ccc/*2]*/ += 1;
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_parameters_scope() async {
// We should only get the parameters declared in the current function.
code = TestCode.parse(r'''
void f(int aaa, int bbb, int ccc) {
void b(int /*[0*/aaa/*0]*/, int /*[1*/bbb/*1]*/) {
var _ = (aaa, bbb, ccc) => null;
var _ = () {
var aaa = 1, bbb = 1, ccc = 1;
};
^
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_property_experimentDisabled() async {
code = TestCode.parse(r'''
void f(String /*[0*/s/*0]*/) {
print(s.length); // No inline value, experiment not enabled
^
}
''');
await verify_values(
code,
// The parameter is a variable even though this test is about properties.
ofType: InlineValueVariableLookup,
);
}
Future<void> test_property_getter() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
void f(String /*[0*/s/*0]*/) {
print(/*[1*/s.length/*1]*/);
print(/*[2*/s.length.isEven/*2]*/);
^
}
''');
await verify_values(
code,
ofTypes: {
0: InlineValueVariableLookup,
1: InlineValueEvaluatableExpression,
2: InlineValueEvaluatableExpression,
},
);
}
Future<void> test_property_getter_enum_value_excluded() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
enum MyEnum {
one,
}
void f(MyEnum x) {
print(/*[0*/x/*0]*/ == MyEnum.one); // MyEnum.one excluded
print(/*[1*/MyEnum.one.index/*1]*/);
^
}
''');
await verify_values(
code,
ofTypes: {
0: InlineValueVariableLookup,
1: InlineValueEvaluatableExpression,
},
);
}
Future<void> test_property_getter_enum_values_excluded() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
enum MyEnum {
one,
}
void f() {
print(MyEnum.values); // MyEnum.values excluded
print(/*[0*/MyEnum.values.length/*0]*/);
^
}
''');
await verify_values(code, ofType: InlineValueEvaluatableExpression);
}
/// Lists are included, iterables are not.
Future<void> test_property_iterables() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
void f(List<int> /*[0*/list/*0]*/, Iterable<int> iterable) {
print(/*[1*/list.length/*1]*/);
print(iterable.length);
^
}
''');
await verify_values(
code,
ofTypes: {
0: InlineValueVariableLookup,
1: InlineValueEvaluatableExpression,
},
);
}
Future<void> test_property_method() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
class A {
void x() {}
}
void f(A /*[0*/a/*0]*/) {
a.x(); // No value for methods.
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_property_method_targets() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
class A {
String x(int a) => a.toString();
}
void f(A /*[0*/a/*0]*/, int b) {
// No value for length because the expression contains a method call.
a.x(b).length;
// No value for either length of isEven because the expression contains a
// method call.
a.x(/*[1*/b/*1]*/).length.isEven;
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
/// Unlike variables, which we include for the line of the execution pointer
/// (to aid with reviewing conditional statements), getters are only evaluated
/// if they are before the execution pointer to reduce the chance of
/// triggering side-effects before the code would have.
Future<void> test_property_range_onlyBeforePointer() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
void f(String /*[0*/s/*0]*/) {
^if (s.isNotEmpty) {
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_property_setter() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
class A {
int? x;
}
void f(A /*[0*/a/*0]*/) {
a.x = 1; // No value for setters.
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_scope_method_inNestedFunction() async {
code = TestCode.parse(r'''
class A {
void method() {
void inner() {
var [!valueVar!] = 1;
^
}
var noValueVar = 1;
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_scope_method_notInNestedFunction() async {
code = TestCode.parse(r'''
class A {
void method() {
void inner() {
var noValueVar = 1;
}
var [!valueVar!] = 1;
^
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_scope_topLevelFunction_inNestedFunction() async {
code = TestCode.parse(r'''
void top() {
void inner() {
var [!valueVar!] = 1;
^
}
var noValueVar = 1;
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_scope_topLevelFunction_notInNestedFunction() async {
code = TestCode.parse(r'''
void top() {
void inner() {
var noValueVar = 1;
}
var [!valueVar!] = 1;
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_variable_declaration() async {
code = TestCode.parse(r'''
void f() {
int /*[0*/aaa/*0]*/ = 1;
int /*[1*/bbb/*1]*/ = 1, /*[2*/ccc/*2]*/ = 1;
^
aaa + bbb;
ccc;
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_variable_forIn() async {
code = TestCode.parse(r'''
void f(List<int> ints) {
for (var /*[0*/i/*0]*/ in /*[1*/ints/*1]*/) {
^
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_variable_forIn_destructure() async {
code = TestCode.parse(r'''
void f(List<(int, int)> records) {
for (var (/*[0*/x/*0]*/, /*[1*/y/*1]*/) in /*[2*/records/*2]*/) {
^
}
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
/// Lists are included, iterables are not.
Future<void> test_variable_iterables() async {
experimentalInlineValuesProperties = true;
code = TestCode.parse(r'''
void f() {
var list = [1,];
var iterable = list as Iterable<int>;
print(/*[0*/list/*0]*/);
print(iterable);
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_variable_propertyAccess() async {
code = TestCode.parse(r'''
void f(int /*[0*/aaa/*0]*/) {
aaa.isEven; // No inline value for var because it's in a property access.
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_variable_read() async {
code = TestCode.parse(r'''
void f() {
int aaa = 1;
int bbb = 1, ccc = 1;
^/*[0*/aaa/*0]*/ + /*[1*/bbb/*1]*/ + /*[2*/ccc/*2]*/;
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_variable_write() async {
code = TestCode.parse(r'''
void f() {
int aaa = 0, bbb = 0, ccc = 0;
/*[0*/aaa/*0]*/ = 1;
/*[1*/bbb/*1]*/++;
/*[2*/ccc/*2]*/ += 1;
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
Future<void> test_variables_scope() async {
// We should not get the top-levels or the nested functions.
code = TestCode.parse(r'''
var aaa = 1;
var bbb = 1;
void f() {
int /*[0*/aaa/*0]*/ = 0, /*[1*/ccc/*1]*/ = 0;
var _ = (aaa, bbb, ccc) => null;
var _ = () {
var aaa = 1, bbb = 1, ccc = 1;
};
^
}
''');
await verify_values(code, ofType: InlineValueVariableLookup);
}
/// Verifies [code] produces values at the marked ranges.
///
/// The [ofTypes] contains the kind of value to be expected for the range with
/// the same index. If a range is not included in [ofTypes] then [ofType] is
/// used instead.
Future<void> verify_values(
TestCode code, {
Type? ofType,
Map<int, Type>? ofTypes,
}) async {
await provideConfig(initialize, {
if (experimentalInlineValuesProperties)
'experimentalInlineValuesProperties': true,
});
await openFile(mainFileUri, code.code);
await initialAnalysis;
var actualValues = await getInlineValues(
mainFileUri,
visibleRange: rangeOfWholeContent(code.code),
stoppedAt: code.position.position,
);
var expectedValues = code.ranges.ranges.mapIndexed((index, range) {
return switch (ofTypes?[index] ?? ofType) {
const (InlineValueVariableLookup) => InlineValue.t3(
InlineValueVariableLookup(caseSensitiveLookup: true, range: range),
),
const (InlineValueEvaluatableExpression) => InlineValue.t1(
InlineValueEvaluatableExpression(range: range),
),
_ => throw 'No type provided for range $index',
};
});
expect(actualValues, unorderedEquals(expectedValues));
}
}