blob: 6255d0de9318de14d6282b8f18ff957d050b923d [file] [log] [blame]
// Copyright (c) 2018, 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:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/lint/constants.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/string_source.dart';
import 'package:analyzer/src/workspace/pub.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(CanBeConstConstructorTest);
defineReflectiveTests(CanBeConstInstanceCreationTest);
defineReflectiveTests(CanBeConstTypedLiteralTest);
defineReflectiveTests(EvaluateExpressionTest);
defineReflectiveTests(PubDependencyTest);
});
}
@reflectiveTest
abstract class AbstractLinterContextTest extends PubPackageResolutionTest {
late final LinterContextWithResolvedResults context;
Future<void> resolve(String content) async {
await resolveTestCode(content);
var errorReporter = ErrorReporter(
RecordingErrorListener(),
StringSource(result.content, null),
);
var contextUnit = LintRuleUnitContext(
file: result.file,
content: result.content,
errorReporter: errorReporter,
unit: result.unit,
);
var libraryElement = result.libraryElement;
var analysisContext = libraryElement.session.analysisContext;
var libraryPath = libraryElement.source.fullName;
var workspace = analysisContext.contextRoot.workspace;
var workspacePackage = workspace.findPackageFor(libraryPath);
context = LinterContextWithResolvedResults(
[contextUnit],
contextUnit,
result.typeProvider,
result.typeSystem as TypeSystemImpl,
InheritanceManager3(),
// TODO(pq): Use a test package or consider passing in `null`.
workspacePackage,
);
}
}
@reflectiveTest
class CanBeConstConstructorTest extends AbstractLinterContextTest {
void assertCanBeConstConstructor(String search, bool expectedResult) {
var constructor =
findNode.constructor(search) as ConstructorDeclarationImpl;
expect(constructor.canBeConst, expectedResult);
}
test_assertInitializer_parameter() async {
await resolve(r'''
class C {
C(int a) : assert(a >= 0, 'error');
}
''');
assertCanBeConstConstructor('C(int a)', true);
}
test_empty() async {
await resolve(r'''
class C {
C();
}
''');
assertCanBeConstConstructor('C()', true);
}
test_field_notConstInitializer() async {
await resolve(r'''
class C {
final int f = a;
C();
}
var a = 0;
''');
assertCanBeConstConstructor('C()', false);
}
test_field_notFinal() async {
await resolve(r'''
class C {
int f = 0;
C();
}
''');
assertCanBeConstConstructor('C()', false);
}
test_field_notFinal_inherited() async {
await resolve(r'''
class A {
int f = 0;
}
class B extends A {
B();
}
''');
assertCanBeConstConstructor('B()', false);
}
test_fieldInitializer_literal() async {
await resolve(r'''
class C {
final int f;
C() : f = 0;
}
''');
assertCanBeConstConstructor('C()', true);
}
test_fieldInitializer_notConst() async {
await resolve(r'''
class C {
final int f;
C() : f = a;
}
var a = 0;
''');
assertCanBeConstConstructor('C()', false);
}
test_fieldInitializer_parameter() async {
await resolve(r'''
class C {
final int f;
C(int a) : f = a;
}
''');
assertCanBeConstConstructor('C(int a)', true);
}
}
@reflectiveTest
class CanBeConstInstanceCreationTest extends AbstractLinterContextTest {
void assertCanBeConst(String snippet, bool expectedResult) {
var node = findNode.instanceCreation(snippet);
expect(node.canBeConst, expectedResult);
}
void test_deferred_argument() async {
await resolveFileCode('$testPackageLibPath/a.dart', r'''
class A {
const A();
}
const aa = A();
''');
await resolve(r'''
import 'a.dart' deferred as a;
class B {
const B(Object a);
}
main() {
print(B(a.aa));
}
''');
assertCanBeConst('B(a.aa)', false);
}
void test_false_argument_invocation() async {
await resolve('''
class A {}
class B {
const B(A a);
}
A f() => A();
B g() => B(f());
''');
assertCanBeConst("B(f", false);
}
void test_false_argument_invocationInList() async {
await resolve('''
class A {}
class B {
const B(a);
}
A f() => A();
B g() => B([f()]);
''');
assertCanBeConst("B([", false);
}
void test_false_argument_nonConstConstructor() async {
await resolve('''
class A {}
class B {
const B(A a);
}
B f() => B(A());
''');
assertCanBeConst("B(A(", false);
}
void test_false_constructorReference_typeParameter() async {
await resolve('''
class A<T> {
const A();
}
class B<T> {
final A<T> Function() fn;
const B(this.fn);
}
B<T> fn<T>() => B(A<T>.new);
''');
assertCanBeConst('B(A<T>.new)', false);
}
void test_false_mapKeyType_implementsEqual() async {
await resolve('''
class A {
const A();
bool operator ==(other) => false;
}
class B {
const B(_);
}
main() {
B({A(): 0});
}
''');
assertCanBeConst("B({", false);
}
void test_false_nonConstConstructor() async {
await resolve('''
class A {}
A f() => A();
''');
assertCanBeConst("A(", false);
}
void test_false_setElementType_implementsEqual() async {
await resolve('''
class A {
const A();
bool operator ==(other) => false;
}
class B {
const B(_);
}
main() {
B({A()});
}
''');
assertCanBeConst("B({", false);
}
void test_false_typeParameter() async {
await resolve('''
class A<T> {
const A();
}
f<U>() => A<U>();
''');
assertCanBeConst("A<U>", false);
}
void test_true_argument_list_nonBool() async {
await resolve('''
const bool kIsWeb = bool.fromEnvironment('dart.library.js_util');
class A {
const A(List<int> l);
}
A f() => A([if (!kIsWeb) ...[1, 2, 3] else ...[1]]);
''');
assertCanBeConst("A([", true);
}
void test_true_computeDependencies() async {
newFile('$testPackageLibPath/a.dart', r'''
const a = 0;
''');
await resolve('''
import 'a.dart';
class A {
const A(int a);
}
A f() => A(a);
''');
assertCanBeConst('A(a)', true);
}
void test_true_constConstructor_instance() async {
newFile('$testPackageLibPath/a.dart', '''
class A {
static const A instance = const A();
const A();
}
''');
await resolve('''
import 'a.dart';
class B {
final A v;
const B(this.v);
}
B f1() => B(A.instance);
''');
assertCanBeConst('B(A.instance)', true);
}
void test_true_constConstructorArg() async {
await resolve('''
class A {
const A();
}
class B {
const B(A a);
}
B f() => B(A());
''');
assertCanBeConst("B(A(", true);
}
void test_true_constListArg() async {
await resolve('''
class A {
const A(List<int> l);
}
A f() => A([1, 2, 3]);
''');
assertCanBeConst("A([", true);
}
void test_true_importedClass_defaultValue() async {
var aPath = convertPath('$testPackageLibPath/a.dart');
newFile(aPath, r'''
class A {
final int a;
const A({int b = 1}) : a = b * 2;
}
''');
await resolve('''
import 'a.dart';
A f() => A();
''');
assertCanBeConst("A();", true);
}
}
@reflectiveTest
class CanBeConstTypedLiteralTest extends AbstractLinterContextTest {
void assertCanBeConst(String snippet, bool expectedResult) {
var node = findNode.typedLiteral(snippet);
expect(node.canBeConst, expectedResult);
}
void test_listLiteral_false_forElement() async {
await resolve('''
f() => [for (var i = 0; i < 10; i++) i];
''');
assertCanBeConst('[for', false);
}
void test_listLiteral_false_methodInvocation() async {
await resolve('''
f() => [g()];
int g() => 0;
''');
assertCanBeConst('[', false);
}
void test_listLiteral_false_typeParameter() async {
await resolve('''
class A<T> {
const A();
}
f<U>() => [A<U>()];
''');
assertCanBeConst('[', false);
}
void test_listLiteral_true_computeDependencies() async {
newFile('$testPackageLibPath/a.dart', r'''
const a = 0;
''');
await resolve('''
import 'a.dart';
f() => [a];
''');
assertCanBeConst('[', true);
}
void test_listLiteral_true_constConstructor() async {
await resolve('''
class A {
const A();
}
f() => [A()];
''');
assertCanBeConst('[', true);
}
void test_listLiteral_true_ifElement() async {
await resolve('''
const a = true;
f() => [if (a) 0 else 1];
''');
assertCanBeConst('[if', true);
}
void test_listLiteral_true_integerLiteral() async {
await resolve('''
f() => [1, 2, 3];
''');
assertCanBeConst('[', true);
}
void test_mapLiteral_false_forElement() async {
await resolve('''
f() => {for (var i = 0; i < 10; i++) i: 0};
''');
assertCanBeConst('{', false);
}
void test_mapLiteral_false_methodInvocation_key() async {
await resolve('''
f() => {g(): 0};
int g() => 0;
''');
assertCanBeConst('{', false);
}
void test_mapLiteral_false_methodInvocation_value() async {
await resolve('''
f() => {0: g()};
int g() => 0;
''');
assertCanBeConst('{', false);
}
void test_mapLiteral_true_ifElement() async {
await resolve('''
const a = true;
f() => {if (a) 0: 0 else 1: 1};
''');
assertCanBeConst('{', true);
}
void test_mapLiteral_true_integerLiteral() async {
await resolve('''
f() => {1: 2, 3: 4};
''');
assertCanBeConst('{', true);
}
void test_setLiteral_false_forElement() async {
await resolve('''
f() => {for (var i = 0; i < 10; i++) i};
''');
assertCanBeConst('{for', false);
}
void test_setLiteral_false_methodInvocation() async {
await resolve('''
f() => {g()};
int g() => 0;
''');
assertCanBeConst('{', false);
}
void test_setLiteral_true_ifElement() async {
await resolve('''
const a = true;
f() => {if (a) 0 else 1};
''');
assertCanBeConst('{', true);
}
void test_setLiteral_true_integerLiteral() async {
await resolve('''
f() => {1, 2, 3};
''');
assertCanBeConst('{', true);
}
}
@reflectiveTest
class EvaluateExpressionTest extends AbstractLinterContextTest {
test_hasError_listLiteral_forElement() async {
await resolve('''
var x = const [for (var i = 0; i < 4; i++) i];
''');
var result = _evaluateX();
expect(result.errors, isNotEmpty);
expect(result.value, isNull);
}
test_hasError_mapLiteral_forElement() async {
await resolve('''
var x = const {for (var i = 0; i < 4; i++) i: 0};
''');
var result = _evaluateX();
expect(result.errors, isNotEmpty);
expect(result.value, isNull);
}
test_hasError_methodInvocation() async {
await resolve('''
var x = 42.abs();
''');
var result = _evaluateX();
expect(result.errors, isNotEmpty);
expect(result.value, isNull);
}
test_hasError_setLiteral_forElement() async {
await resolve('''
var x = const {for (var i = 0; i < 4; i++) i};
''');
var result = _evaluateX();
expect(result.errors, isNotEmpty);
expect(result.value, isNull);
}
test_hasValue_binaryExpression() async {
await resolve('''
var x = 1 + 2;
''');
var result = _evaluateX();
expect(result.errors, isEmpty);
expect(result.value!.toIntValue(), 3);
}
test_hasValue_constantReference() async {
await resolve('''
const a = 42;
var x = a;
''');
var result = _evaluateX();
expect(result.errors, isEmpty);
expect(result.value!.toIntValue(), 42);
}
test_hasValue_constantReference_imported() async {
newFile('$testPackageLibPath/a.dart', r'''
const a = 42;
''');
await resolve('''
import 'a.dart';
var x = a;
''');
var result = _evaluateX();
expect(result.errors, isEmpty);
expect(result.value!.toIntValue(), 42);
}
test_hasValue_intLiteral() async {
await resolve('''
var x = 42;
''');
var result = _evaluateX();
expect(result.errors, isEmpty);
expect(result.value!.toIntValue(), 42);
}
LinterConstantEvaluationResult _evaluateX() {
var node = findNode.topVariableDeclarationByName('x').initializer!;
return node.computeConstantValue();
}
}
@reflectiveTest
class PubDependencyTest extends AbstractLinterContextTest {
test_dependencies() async {
newPubspecYamlFile(testPackageRootPath, '''
name: test
dependencies:
args: '>=0.12.1 <2.0.0'
charcode: ^1.1.0
''');
await resolve(r'''
/// Dummy class.
class C { }
''');
expect(context.package, TypeMatcher<PubPackage>());
var pubPackage = context.package as PubPackage;
var pubspec = pubPackage.pubspec!;
var argsDep = pubspec.dependencies!
.singleWhere((element) => element.name!.text == 'args');
expect(argsDep.version!.value.text, '>=0.12.1 <2.0.0');
var charCodeDep = pubspec.dependencies!
.singleWhere((element) => element.name!.text == 'charcode');
expect(charCodeDep.version!.value.text, '^1.1.0');
}
}