blob: 4457f9766474645c13311d40f08a38c6066e7d29 [file] [log] [blame]
// Copyright (c) 2022, 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.
// ignore_for_file: library_private_types_in_public_api
import 'package:analysis_server/src/utilities/import_analyzer.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:test/expect.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../analysis_server_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ImportAnalyzerDeclarationsInclusionTest);
defineReflectiveTests(ImportAnalyzerDeclarationsKindTest);
defineReflectiveTests(ImportAnalyzerReferenceInclusionTest);
defineReflectiveTests(ImportAnalyzerReferenceKindTest);
defineReflectiveTests(ImportAnalyzerReferencePrefixTest);
});
}
/// Tests that show that the correct set of declarations are considered to be
/// moving or staying.
@reflectiveTest
class ImportAnalyzerDeclarationsInclusionTest extends ImportAnalyzerTest {
Future<void> test_multiple_all() async {
await analyze(
'''
[!
class A {}
class B {}
class C {}
class D {}
!]
''',
expectedMovingDeclarations: [
element<ClassElement>(name: 'A'),
element<ClassElement>(name: 'B'),
element<ClassElement>(name: 'C'),
element<ClassElement>(name: 'D'),
],
expectedStayingDeclarations: [],
);
}
Future<void> test_multiple_first() async {
await analyze(
'''
[!
class A {}
class B {}
!]
class C {}
class D {}
''',
expectedMovingDeclarations: [
element<ClassElement>(name: 'A'),
element<ClassElement>(name: 'B'),
],
expectedStayingDeclarations: [
element<ClassElement>(name: 'C'),
element<ClassElement>(name: 'D'),
],
);
}
Future<void> test_multiple_last() async {
await analyze(
'''
class A {}
class B {}
[!
class C {}
class D {}
!]
''',
expectedMovingDeclarations: [
element<ClassElement>(name: 'C'),
element<ClassElement>(name: 'D'),
],
expectedStayingDeclarations: [
element<ClassElement>(name: 'A'),
element<ClassElement>(name: 'B'),
],
);
}
Future<void> test_multiple_middle() async {
await analyze(
'''
class A {}
[!
class B {}
class C {}
!]
class D {}
''',
expectedMovingDeclarations: [
element<ClassElement>(name: 'B'),
element<ClassElement>(name: 'C'),
],
expectedStayingDeclarations: [
element<ClassElement>(name: 'A'),
element<ClassElement>(name: 'D'),
],
);
}
Future<void> test_single_first() async {
await analyze(
'''
[!
class C {}
!]
class D {}
''',
expectedMovingDeclarations: [element<ClassElement>(name: 'C')],
expectedStayingDeclarations: [element<ClassElement>(name: 'D')],
);
}
Future<void> test_single_last() async {
await analyze(
'''
class B {}
[!
class C {}
!]
''',
expectedMovingDeclarations: [element<ClassElement>(name: 'C')],
expectedStayingDeclarations: [element<ClassElement>(name: 'B')],
);
}
Future<void> test_single_middle() async {
await analyze(
'''
class B {}
[!
class C {}
!]
class D {}
''',
expectedMovingDeclarations: [element<ClassElement>(name: 'C')],
expectedStayingDeclarations: [
element<ClassElement>(name: 'B'),
element<ClassElement>(name: 'D'),
],
);
}
Future<void> test_single_only() async {
await analyze(
'''
[!
class C {}
!]
''',
expectedMovingDeclarations: [element<ClassElement>(name: 'C')],
expectedStayingDeclarations: [],
);
}
}
/// Tests that show that every kind of declaration is correctly recognized.
@reflectiveTest
class ImportAnalyzerDeclarationsKindTest extends ImportAnalyzerTest {
Future<void> test_all() async {
await analyze(
'''
[!
class C1 {}
class D1 = C1 with M1;
enum E1 {}
typedef void F1();
typedef G1 = void Function();
mixin M1 {}
typedef T1 = C1;
extension X1 on int {}
void f1() {}
int get g1 => 0;
int set s1(x) {}
int v1 = 0;
!]
class C2 {}
class D2 = C2 with M2;
enum E2 {}
typedef void F2();
typedef G2 = void Function();
mixin M2 {}
typedef T2 = C2;
extension X2 on int {}
void f2() {}
int get g2 => 0;
int set s2(x) {}
int v2 = 0;
''',
expectedMovingDeclarations: [
element<ClassElement>(name: 'C1'),
element<ClassElement>(name: 'D1'),
element<EnumElement>(name: 'E1'),
element<TypeAliasElement>(name: 'F1'),
element<TypeAliasElement>(name: 'G1'),
element<MixinElement>(name: 'M1'),
element<TypeAliasElement>(name: 'T1'),
element<ExtensionElement>(name: 'X1'),
element<TopLevelFunctionElement>(name: 'f1'),
element<GetterElement>(name: 'g1'),
element<SetterElement>(name: 's1'),
element<TopLevelVariableElement>(name: 'v1'),
],
expectedStayingDeclarations: [
element<ClassElement>(name: 'C2'),
element<ClassElement>(name: 'D2'),
element<EnumElement>(name: 'E2'),
element<TypeAliasElement>(name: 'F2'),
element<TypeAliasElement>(name: 'G2'),
element<MixinElement>(name: 'M2'),
element<TypeAliasElement>(name: 'T2'),
element<ExtensionElement>(name: 'X2'),
element<TopLevelFunctionElement>(name: 'f2'),
element<GetterElement>(name: 'g2'),
element<SetterElement>(name: 's2'),
element<TopLevelVariableElement>(name: 'v2'),
],
);
}
}
/// Tests that show that references are correctly recognized as either being in
/// the moving or staying code.
@reflectiveTest
class ImportAnalyzerReferenceInclusionTest extends ImportAnalyzerTest {
Future<void> test_movingReferencesImported() async {
await analyze(
'''
import 'dart:io';
[!
class C {
File? file;
}
!]
''',
expectedStayingReferences: {},
expectedMovingReferences: {
element<ClassElement>(name: 'File'): [''],
},
);
}
Future<void> test_movingReferencesMoving() async {
await analyze(
'''
[!
class C {
C? parent;
}
!]
''',
expectedStayingReferences: {},
expectedMovingReferences: {},
);
}
Future<void> test_movingReferencesStaying() async {
await analyze(
'''
class A {}
[!
class C extends A {}
!]
''',
expectedStayingReferences: {},
expectedMovingReferences: {
element<ClassElement>(name: 'A'): [''],
},
);
}
Future<void> test_stayingReferencesImported() async {
await analyze(
'''
import 'dart:io';
class A {
File? file;
}
[!
class C {}
!]
''',
expectedStayingReferences: {
element<ClassElement>(name: 'File'): [''],
},
expectedMovingReferences: {},
);
}
Future<void> test_stayingReferencesMoving() async {
await analyze(
'''
class A extends C {}
[!
class C {}
!]
''',
expectedStayingReferences: {
element<ClassElement>(name: 'C'): [''],
},
expectedMovingReferences: {},
);
}
Future<void> test_stayingReferencesStaying() async {
await analyze(
'''
class A {
A? parent;
}
[!
class C {}
!]
''',
expectedStayingReferences: {},
expectedMovingReferences: {},
);
}
}
/// Tests that show that references are correctly recognized for every kind of
/// element.
@reflectiveTest
class ImportAnalyzerReferenceKindTest extends ImportAnalyzerTest {
Future<void> test_class_movingDeclaration() async {
await analyze(
'''
C? c;
[!
class C {}
!]
''',
expectedStayingReferences: {
element<ClassElement>(name: 'C'): [''],
},
);
}
Future<void> test_class_movingReference() async {
await analyze(
'''
class C {}
[!
C? c;
!]
''',
expectedMovingReferences: {
element<ClassElement>(name: 'C'): [''],
},
);
}
Future<void> test_enum_movingDeclaration() async {
await analyze(
'''
E? e;
[!
enum E {}
!]
''',
expectedStayingReferences: {
element<EnumElement>(name: 'E'): [''],
},
);
}
Future<void> test_enum_movingReference() async {
await analyze(
'''
enum E {}
[!
E? e;
!]
''',
expectedMovingReferences: {
element<EnumElement>(name: 'E'): [''],
},
);
}
Future<void> test_extension_member_movingDeclaration() async {
await analyze(
'''
var x = 2.g;
[!
extension X on int {
int get g => this;
}
!]
''',
expectedStayingReferences: {
element<ExtensionElement>(name: 'X'): [''],
},
);
}
Future<void> test_extension_member_movingReference() async {
await analyze(
'''
extension X on int {
int get g => this;
}
[!
var x = 2.g;
!]
''',
expectedMovingReferences: {
element<ExtensionElement>(name: 'X'): [''],
},
);
}
Future<void> test_extension_operator_movingDeclaration() async {
await analyze(
'''
class A {}
var x = A() + A();
[!
extension X on A {
A operator +(A other) => A();
}
!]
''',
expectedStayingReferences: {
element<ExtensionElement>(name: 'X'): [''],
},
);
}
Future<void> test_extension_operator_movingReference() async {
await analyze(
'''
class A {}
extension X on A {
A operator +(A other) => A();
}
[!
var x = A() + A();
!]
''',
expectedMovingReferences: {
element<ExtensionElement>(name: 'X'): [''],
element<ClassElement>(name: 'A'): [''],
},
);
}
Future<void> test_extension_override_movingDeclaration() async {
await analyze(
'''
var x = X(2).g;
[!
extension X on int {
int get g => this;
}
!]
''',
expectedStayingReferences: {
element<ExtensionElement>(name: 'X'): [''],
},
);
}
Future<void> test_extension_override_movingReference() async {
await analyze(
'''
extension X on int {
int get g => this;
}
[!
var x = X(2).g;
!]
''',
expectedMovingReferences: {
element<ExtensionElement>(name: 'X'): [''],
},
);
}
Future<void> test_functionTypeAlias_movingDeclaration() async {
await analyze(
'''
F? f;
[!
typedef void F();
!]
''',
expectedStayingReferences: {
element<TypeAliasElement>(name: 'F'): [''],
},
);
}
Future<void> test_functionTypeAlias_movingReference() async {
await analyze(
'''
typedef void F();
[!
F? f;
!]
''',
expectedMovingReferences: {
element<TypeAliasElement>(name: 'F'): [''],
},
);
}
Future<void> test_genericFunctionTypeAlias_movingDeclaration() async {
await analyze(
'''
G? g;
[!
typedef G = void Function();
!]
''',
expectedStayingReferences: {
element<TypeAliasElement>(name: 'G'): [''],
},
);
}
Future<void> test_genericFunctionTypeAlias_movingReference() async {
await analyze(
'''
typedef G = void Function();
[!
G? g;
!]
''',
expectedMovingReferences: {
element<TypeAliasElement>(name: 'G'): [''],
},
);
}
Future<void> test_mixin_movingDeclaration() async {
await analyze(
'''
M? m;
[!
mixin M {}
!]
''',
expectedStayingReferences: {
element<MixinElement>(name: 'M'): [''],
},
);
}
Future<void> test_mixin_movingReference() async {
await analyze(
'''
mixin M {}
[!
M? m;
!]
''',
expectedMovingReferences: {
element<MixinElement>(name: 'M'): [''],
},
);
}
Future<void> test_mixinApplication_movingDeclaration() async {
await analyze(
'''
D? d;
[!
class D = C with M;
class C {}
mixin M {}
!]
''',
expectedStayingReferences: {
element<ClassElement>(name: 'D'): [''],
},
);
}
Future<void> test_mixinApplication_movingReference() async {
await analyze(
'''
class D = C with M;
class C {}
mixin M {}
[!
D? d;
!]
''',
expectedMovingReferences: {
element<ClassElement>(name: 'D'): [''],
},
);
}
Future<void> test_topLevelFunction_movingDeclaration() async {
await analyze(
'''
var x = f();
[!
void f() {}
!]
''',
expectedStayingReferences: {
element<TopLevelFunctionElement>(name: 'f'): [''],
},
);
}
Future<void> test_topLevelFunction_movingReference() async {
await analyze(
'''
void f() {}
[!
var x = f();
!]
''',
expectedMovingReferences: {
element<TopLevelFunctionElement>(name: 'f'): [''],
},
);
}
Future<void> test_topLevelGetter_movingDeclaration() async {
await analyze(
'''
var x = g;
[!
int get g => 0;
!]
''',
expectedStayingReferences: {
element<GetterElement>(name: 'g'): [''],
},
);
}
Future<void> test_topLevelGetter_movingReference() async {
await analyze(
'''
int get g => 0;
[!
var x = g;
!]
''',
expectedMovingReferences: {
element<GetterElement>(name: 'g'): [''],
},
);
}
Future<void> test_topLevelSetter_movingDeclaration() async {
await analyze(
'''
void f() {
s = 3;
}
[!
int set s(x) {}
!]
''',
expectedStayingReferences: {
element<SetterElement>(name: 's'): [''],
},
);
}
Future<void> test_topLevelSetter_movingReference() async {
await analyze(
'''
int set s(x) {}
[!
void f() {
s = 3;
}
!]
''',
expectedMovingReferences: {
element<SetterElement>(name: 's'): [''],
},
);
}
Future<void>
test_topLevelVariable_compoundAssignment_separateReadWriteElements_movingDeclaration() async {
await analyze(
'''
void f() => v += 1;
[!
int get v => 0;
set v(num _) {}
!]
''',
expectedStayingReferences: {
element<SetterElement>(name: 'v'): [''],
element<GetterElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_compoundAssignment_separateReadWriteElements_movingReference() async {
await analyze(
'''
int get v => 0;
set v(num _) {}
[!
void f() => v += 1;
!]
''',
expectedMovingReferences: {
element<SetterElement>(name: 'v'): [''],
element<GetterElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_compoundAssignment_singleReadWriteElement_movingDeclaration() async {
await analyze(
'''
void f() => v += 1;
[!
int v = 0;
!]
''',
expectedStayingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_compoundAssignment_singleReadWriteElement_movingReference() async {
await analyze(
'''
int v = 0;
[!
void f() => v += 1;
!]
''',
expectedMovingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void> test_topLevelVariable_movingDeclaration() async {
await analyze(
'''
var x = v;
[!
int v = 0;
!]
''',
expectedStayingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void> test_topLevelVariable_movingReference() async {
await analyze(
'''
int v = 0;
[!
var x = v;
!]
''',
expectedMovingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_postfixIncrement_separateReadWriteElements_movingDeclaration() async {
await analyze(
'''
void f() => v++;
[!
int get v => 0;
set v(num _) {}
!]
''',
expectedStayingReferences: {
element<SetterElement>(name: 'v'): [''],
element<GetterElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_postfixIncrement_separateReadWriteElements_movingReference() async {
await analyze(
'''
int get v => 0;
set v(num _) {}
[!
void f() => v++;
!]
''',
expectedMovingReferences: {
element<SetterElement>(name: 'v'): [''],
element<GetterElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_postfixIncrement_singleReadWriteElement_movingDeclaration() async {
await analyze(
'''
void f() => v++;
[!
int v = 0;
!]
''',
expectedStayingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_postfixIncrement_singleReadWriteElement_movingReference() async {
await analyze(
'''
int v = 0;
[!
void f() => v++;
!]
''',
expectedMovingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_prefixIncrement_separateReadWriteElements_movingDeclaration() async {
await analyze(
'''
void f() => ++v;
[!
int get v => 0;
set v(num _) {}
!]
''',
expectedStayingReferences: {
element<SetterElement>(name: 'v'): [''],
element<GetterElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_prefixIncrement_separateReadWriteElements_movingReference() async {
await analyze(
'''
int get v => 0;
set v(num _) {}
[!
void f() => ++v;
!]
''',
expectedMovingReferences: {
element<SetterElement>(name: 'v'): [''],
element<GetterElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_prefixIncrement_singleReadWriteElement_movingDeclaration() async {
await analyze(
'''
void f() => ++v;
[!
int v = 0;
!]
''',
expectedStayingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void>
test_topLevelVariable_prefixIncrement_singleReadWriteElement_movingReference() async {
await analyze(
'''
int v = 0;
[!
void f() => ++v;
!]
''',
expectedMovingReferences: {
element<TopLevelVariableElement>(name: 'v'): [''],
},
);
}
Future<void> test_typeAlias_movingDeclaration() async {
await analyze(
'''
T? t;
[!
typedef T = C;
class C {}
!]
''',
expectedStayingReferences: {
element<TypeAliasElement>(name: 'T'): [''],
},
);
}
Future<void> test_typeAlias_movingReference() async {
await analyze(
'''
typedef T = C;
class C {}
[!
T? t;
!]
''',
expectedMovingReferences: {
element<TypeAliasElement>(name: 'T'): [''],
},
);
}
}
/// Tests that show that the prefixes used in references are correctly captured.
@reflectiveTest
class ImportAnalyzerReferencePrefixTest extends ImportAnalyzerTest {
Future<void> test_class_movingDeclaration() async {
await analyze(
'''
import 'dart:io';
import 'dart:io' as a;
import 'dart:io' as b;
import 'dart:io' as c;
import 'dart:io' as d;
[!
File? f1;
a.File? f2;
b.File? f3;
!]
Directory? d1;
c.Directory? d2;
d.Directory? d3;
''',
expectedMovingReferences: {
element<ClassElement>(name: 'File'): ['', 'a', 'b'],
},
expectedStayingReferences: {
element<ClassElement>(name: 'Directory'): ['', 'c', 'd'],
},
);
}
}
abstract class ImportAnalyzerTest extends PubPackageAnalysisServerTest {
Future<void> analyze(
String code, {
List<_ExpectedElement>? expectedMovingDeclarations,
List<_ExpectedElement>? expectedStayingDeclarations,
Map<_ExpectedElement, List<String>>? expectedMovingReferences,
Map<_ExpectedElement, List<String>>? expectedStayingReferences,
}) async {
createDefaultFiles();
var testCode = TestCode.parse(code);
var pathToInclude = newFile(testFilePath, testCode.code).path;
var context = AnalysisContextCollection(
includedPaths: [pathToInclude],
resourceProvider: resourceProvider,
sdkPath: sdkRoot.path,
).contexts[0];
var result =
await context.currentSession.getResolvedLibrary(pathToInclude)
as ResolvedLibraryResult;
var analyzer = ImportAnalyzer(result, pathToInclude, [
testCode.range.sourceRange,
]);
if (expectedMovingDeclarations != null) {
var movingDeclarations = analyzer.movingDeclarations.toList();
var length = expectedMovingDeclarations.length;
expect(movingDeclarations, hasLength(length));
for (var i = 0; i < length; i++) {
expectedMovingDeclarations[i].assertMatches(movingDeclarations[i]);
}
}
if (expectedStayingDeclarations != null) {
var stayingDeclarations = analyzer.stayingDeclarations.toList();
var length = expectedStayingDeclarations.length;
expect(stayingDeclarations, hasLength(length));
for (var i = 0; i < length; i++) {
expectedStayingDeclarations[i].assertMatches(stayingDeclarations[i]);
}
}
if (expectedMovingReferences != null) {
var movingEntries = analyzer.movingReferences.entries.toList();
var expectedEntries = expectedMovingReferences.entries.toList();
var length = expectedMovingReferences.length;
expect(movingEntries, hasLength(length));
for (var i = 0; i < length; i++) {
var expectedEntry = expectedEntries[i];
var movingEntry = movingEntries[i];
expectedEntry.key.assertMatches(movingEntry.key);
expect(expectedEntry.value, expectedEntry.value);
}
}
if (expectedStayingReferences != null) {
var stayingEntries = analyzer.stayingReferences.entries.toList();
var expectedEntries = expectedStayingReferences.entries.toList();
var length = expectedStayingReferences.length;
expect(stayingEntries, hasLength(length));
for (var i = 0; i < length; i++) {
var expectedEntry = expectedEntries[i];
var stayingEntry = stayingEntries[i];
expectedEntry.key.assertMatches(stayingEntry.key);
}
}
}
_ExpectedElement<T> element<T extends Element>({required String name}) =>
_ExpectedElement<T>(name: name);
}
class _ExpectedElement<T extends Element> {
final String name;
_ExpectedElement({required this.name});
void assertMatches(Element element) {
expect(element, isA<T>());
expect(element.name, name);
}
}