From facc499fb9a3383e967a70af0b1f84a40e8a40fb Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Fri, 11 Aug 2023 13:51:48 -0700 Subject: [PATCH] extension type support for `prefer_constructors_over_static_methods` (#4680) --- ...efer_constructors_over_static_methods.dart | 42 ++++++++------- ...constructors_over_static_methods_test.dart | 53 ++++++++++++------- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/lib/src/rules/prefer_constructors_over_static_methods.dart b/lib/src/rules/prefer_constructors_over_static_methods.dart index b86bb5e6e..61ebb5eda 100644 --- a/lib/src/rules/prefer_constructors_over_static_methods.dart +++ b/lib/src/rules/prefer_constructors_over_static_methods.dart @@ -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', @@ -101,23 +101,29 @@ class _Visitor extends SimpleAstVisitor { @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 + }; +} diff --git a/test/rules/prefer_constructors_over_static_methods_test.dart b/test/rules/prefer_constructors_over_static_methods_test.dart index fa28f42f1..15a2770a5 100644 --- a/test/rules/prefer_constructors_over_static_methods_test.dart +++ b/test/rules/prefer_constructors_over_static_methods_test.dart @@ -14,9 +14,23 @@ main() { @reflectiveTest class PreferConstructorsOverStaticMethodsTest extends LintRuleTest { + @override + List 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 { @@ -50,31 +64,23 @@ class A { ]); } - test_staticMethod_generic() async { - await assertNoDiagnostics(r''' -class A { - A.named(); - static A generic() => A.named(); -} -'''); - } - - test_staticMethod_returnsInstantiatedInstance() async { - await assertNoDiagnostics(r''' -class A { - A.named(); - static A 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() => A.named(); } '''); } @@ -104,6 +110,15 @@ class A { '''); } + test_staticMethod_returnsInstantiatedInstance() async { + await assertNoDiagnostics(r''' +class A { + A.named(); + static A staticM() => A.named(); +} +'''); + } + test_staticMethod_returnsNullable() async { await assertNoDiagnostics(r''' class A {