Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

extension type support for prefer_constructors_over_static_methods #4680

Merged
merged 1 commit into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions lib/src/rules/prefer_constructors_over_static_methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ class Point {
```
''';

bool _hasNewInvocation(DartType returnType, FunctionBody body) =>
_BodyVisitor(returnType).containsInstanceCreation(body);

// todo(pq): temporary; remove after renamed class is in the SDK
// ignore: non_constant_identifier_names
LintRule PreferConstructorsInsteadOfStaticMethods() =>
PreferConstructorsOverStaticMethods();

bool _hasNewInvocation(DartType returnType, FunctionBody body) =>
_BodyVisitor(returnType).containsInstanceCreation(body);

class PreferConstructorsOverStaticMethods extends LintRule {
static const LintCode code = LintCode(
'prefer_constructors_over_static_methods',
Expand Down Expand Up @@ -101,23 +101,29 @@ class _Visitor extends SimpleAstVisitor<void> {

@override
void visitMethodDeclaration(MethodDeclaration node) {
if (!node.isStatic) return;
if (node.typeParameters != null) return;
var returnType = node.returnType?.type;
var parent = node.parent;
if (node.isStatic &&
parent is ClassDeclaration &&
returnType is InterfaceType &&
parent.typeParameters == null &&
node.typeParameters == null) {
var declaredElement = parent.declaredElement;
if (declaredElement != null) {
var interfaceType = declaredElement.thisType;
if (!context.typeSystem.isAssignableTo(returnType, interfaceType)) {
return;
}
if (_hasNewInvocation(returnType, node.body)) {
rule.reportLintForToken(node.name);
}
if (returnType is! InterfaceType) return;

var interfaceType = node.parent.typeToCheckOrNull();
if (interfaceType != null) {
if (!context.typeSystem.isAssignableTo(returnType, interfaceType)) {
return;
}
if (_hasNewInvocation(returnType, node.body)) {
rule.reportLintForToken(node.name);
}
}
}
}

extension on AstNode? {
InterfaceType? typeToCheckOrNull() => switch (this) {
ExtensionTypeDeclaration e =>
e.typeParameters == null ? e.declaredElement?.thisType : null,
ClassDeclaration c =>
c.typeParameters == null ? c.declaredElement?.thisType : null,
_ => null
};
}
53 changes: 34 additions & 19 deletions test/rules/prefer_constructors_over_static_methods_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,23 @@ main() {

@reflectiveTest
class PreferConstructorsOverStaticMethodsTest extends LintRuleTest {
@override
List<String> get experiments => ['inline-class'];

@override
String get lintRule => 'prefer_constructors_over_static_methods';

test_extensionMethod() async {
await assertNoDiagnostics(r'''
class A {
A.named();
}
extension E on A {
static A foo() => A.named();
}
''');
}

test_factoryConstructor() async {
await assertNoDiagnostics(r'''
class A {
Expand Down Expand Up @@ -50,31 +64,23 @@ class A {
]);
}

test_staticMethod_generic() async {
await assertNoDiagnostics(r'''
class A {
A.named();
static A generic<T>() => A.named();
}
''');
}

test_staticMethod_returnsInstantiatedInstance() async {
await assertNoDiagnostics(r'''
class A<T> {
A.named();
static A<int> staticM() => A.named();
test_staticMethod_expressionBody_extensionType() async {
// Since the check logic is shared one test should be sufficient to verify
// extension types are supported.
await assertDiagnostics(r'''
extension type E(int i) {
static E make(int i) => E(i);
}
''');
''', [
lint(37, 4),
]);
}

test_extensionMethod() async {
test_staticMethod_generic() async {
await assertNoDiagnostics(r'''
class A {
A.named();
}
extension E on A {
static A foo() => A.named();
static A generic<T>() => A.named();
}
''');
}
Expand Down Expand Up @@ -104,6 +110,15 @@ class A {
''');
}

test_staticMethod_returnsInstantiatedInstance() async {
await assertNoDiagnostics(r'''
class A<T> {
A.named();
static A<int> staticM() => A.named();
}
''');
}

test_staticMethod_returnsNullable() async {
await assertNoDiagnostics(r'''
class A {
Expand Down