From 89172fc8d0b6a62349256cfc5f2aab4b56bed0ed Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 12:59:22 -0800 Subject: [PATCH 01/31] + implicit_reopen --- example/all.yaml | 1 + lib/src/rules.dart | 2 + lib/src/rules/implicit_reopen.dart | 229 +++++++++++++++++++++++++++ test/rules/all.dart | 2 + test/rules/implicit_reopen_test.dart | 195 +++++++++++++++++++++++ 5 files changed, 429 insertions(+) create mode 100644 lib/src/rules/implicit_reopen.dart create mode 100644 test/rules/implicit_reopen_test.dart diff --git a/example/all.yaml b/example/all.yaml index 94b19179c..132be4f51 100644 --- a/example/all.yaml +++ b/example/all.yaml @@ -77,6 +77,7 @@ linter: - hash_and_equals - implementation_imports - implicit_call_tearoffs + - implicit_reopen - invalid_case_patterns - iterable_contains_unrelated_type - join_return_with_assignment diff --git a/lib/src/rules.dart b/lib/src/rules.dart index 62edca78f..2c907cab5 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -83,6 +83,7 @@ import 'rules/flutter_style_todos.dart'; import 'rules/hash_and_equals.dart'; import 'rules/implementation_imports.dart'; import 'rules/implicit_call_tearoffs.dart'; +import 'rules/implicit_reopen.dart'; import 'rules/invalid_case_patterns.dart'; import 'rules/invariant_booleans.dart'; import 'rules/iterable_contains_unrelated_type.dart'; @@ -308,6 +309,7 @@ void registerLintRules({bool inTestMode = false}) { ..register(HashAndEquals()) ..register(ImplementationImports()) ..register(ImplicitCallTearoffs()) + ..register(ImplicitReopen()) ..register(InvariantBooleans()) ..register(InvalidCasePatterns()) ..register(IterableContainsUnrelatedType()) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart new file mode 100644 index 000000000..b1146acf7 --- /dev/null +++ b/lib/src/rules/implicit_reopen.dart @@ -0,0 +1,229 @@ +// Copyright (c) 2023, 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/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; + +import '../analyzer.dart'; + +const _desc = r"Don't implicitly reopen classes or mixins"; + +/// todo(pq): link out to the spec or dart.dev docs. +const _details = r''' +Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers, +authors can control whether classes and mixins allow being implemented, +extended, and/or mixed in from outside of the library where they're defined. +In some cases, it's possible for an author to inadvertantly relax these controls +and implicitly "reopen" a class or mixin. This lint guards against that, +requiring such cases to be made explicit with the +[`@reopen`](https://pub.dev/documentation/meta/latest/meta/reopen-constant.html) +annotation in `package:meta`. + +**BAD:** +```dart +interface class I {} + +class C extends I {} +``` + +**GOOD:** +```dart +interface class I {} + +final class C extends I {} +``` + +```dart +import 'package:meta/meta.dart'; + +interface class I {} + +@reopen +class C extends I {} +``` +'''; + +class ImplicitReopen extends LintRule { + static const LintCode code = LintCode('implicit_reopen', + "The class '{0}' reopens '{1}' because it is not marked '{2}'", + correctionMessage: + "Try marking {0} '{2}' or annotating it with '@reopen'"); + + ImplicitReopen() + : super( + name: 'implicit_reopen', + description: _desc, + details: _details, + state: State.experimental(), + group: Group.errors); + + @override + LintCode get lintCode => code; + + @override + void registerNodeProcessors( + NodeLintRegistry registry, LinterContext context) { + var visitor = _Visitor(this); + registry.addClassDeclaration(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + + _Visitor(this.rule); + + void reportLint( + NamedCompilationUnitMember member, { + required InterfaceElement target, + required InterfaceElement other, + required String reason, + }) { + rule.reportLintForToken(member.name, + arguments: [target.name, other.name, reason]); + } + + @override + void visitClassDeclaration(ClassDeclaration node) { + var classElement = node.declaredElement; + if (classElement == null) return; + if (classElement.hasReopen) return; + + var library = classElement.library; + + // | subtype | supertype | `extends`/`with` | + // | _none_ or `base` | `interface` | lint | + // | `base` | `final` | lint | + if (classElement.hasNoModifiers || classElement.isBase) { + var supertype = classElement.superElement; + if (supertype.library == library) { + if (supertype.isInterface || supertype.isInducedInterface) { + reportLint(node, + target: classElement, other: supertype!, reason: 'interface'); + return; + } + } + + for (var m in classElement.mixins) { + var mixin = m.element; + if (mixin.library != library) continue; + if (mixin.isInterface || mixin.isInducedInterface) { + reportLint(node, + target: classElement, other: mixin, reason: 'interface'); + return; + } + } + } + + if (classElement.isBase) { + var supertype = classElement.superElement; + if (supertype.library == library) { + if (supertype.isFinal || supertype.isInducedFinal) { + reportLint(node, + target: classElement, other: supertype!, reason: 'final'); + return; + } + } + + for (var m in classElement.mixins) { + var mixin = m.element; + if (mixin.library != library) continue; + if (mixin.isFinal || mixin.isInducedFinal) { + reportLint(node, target: classElement, other: mixin, reason: 'final'); + return; + } + } + } + } +} + +extension on InterfaceElement? { + List get interfaces => this?.interfaces ?? []; + + bool get isFinal { + var self = this; + return self != null && self.isFinal; + } + + /// A sealed declaration D is considered final if it has a direct extends or + /// with superinterface which is final, or it has a direct superinterface + /// which is base as well as a direct extends or with superinterface which is + /// interface. + bool get isInducedFinal { + if (!isSealed) return false; + + if (superElement.isFinal) return true; + if (mixins.any((m) => m.element.isFinal)) return true; + + for (var i in interfaces) { + if (i.element.isBase) { + if (superElement.isInterface) return true; + if (mixins.any((m) => m.element.isInterface)) return true; + } + } + + return false; + } + + /// A sealed declaration D is considered interface if it has a direct extends + /// or with superinterface which is interface. + bool get isInducedInterface { + if (!isSealed) return false; + + if (superElement.isInterface) return true; + if (mixins.any((m) => m.element.isInterface)) return true; + + return false; + } + + bool get isInterface { + var self = this; + return self != null && self.isInterface; + } + + bool get isSealed { + var self = this; + return self != null && self.isSealed; + } + + LibraryElement? get library => this?.library; + + List get mixins => this?.mixins ?? []; + + InterfaceElement? get superElement => this?.supertype?.element; +} + +extension on InterfaceElement { + bool get hasNoModifiers => !isInterface && !isBase && !isSealed && !isFinal; + + bool get isBase { + var self = this; + if (self is ClassElement) return self.isBase; + if (self is MixinElement) return self.isBase; + return false; + } + + bool get isFinal { + var self = this; + if (self is ClassElement) return self.isFinal; + if (self is MixinElement) return self.isFinal; + return false; + } + + bool get isInterface { + var self = this; + if (self is ClassElement) return self.isInterface; + if (self is MixinElement) return self.isInterface; + return false; + } + + bool get isSealed { + var self = this; + if (self is ClassElement) return self.isSealed; + if (self is MixinElement) return self.isSealed; + return false; + } +} diff --git a/test/rules/all.dart b/test/rules/all.dart index f08b2623b..090af5791 100644 --- a/test/rules/all.dart +++ b/test/rules/all.dart @@ -42,6 +42,7 @@ import 'discarded_futures_test.dart' as discarded_futures; import 'file_names_test.dart' as file_names; import 'flutter_style_todos_test.dart' as flutter_style_todos; import 'hash_and_equals_test.dart' as hash_and_equals; +import 'implicit_reopen_test.dart' as implicit_reopen; import 'invalid_case_patterns_test.dart' as invalid_case_patterns; import 'library_annotations_test.dart' as library_annotations; import 'library_names_test.dart' as library_names; @@ -133,6 +134,7 @@ void main() { file_names.main(); flutter_style_todos.main(); hash_and_equals.main(); + implicit_reopen.main(); invalid_case_patterns.main(); library_annotations.main(); library_names.main(); diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart new file mode 100644 index 000000000..68d7b1fed --- /dev/null +++ b/test/rules/implicit_reopen_test.dart @@ -0,0 +1,195 @@ +// Copyright (c) 2023, 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:test_reflective_loader/test_reflective_loader.dart'; + +import '../rule_test_support.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(ImplicitReopenTest); + }); +} + +@reflectiveTest +class ImplicitReopenTest extends LintRuleTest { + @override + bool get addMetaPackageDep => true; + + @override + List get experiments => ['class-modifiers', 'sealed-class']; + + @override + String get lintRule => 'implicit_reopen'; + + test_extends_class_classFinal_ok() async { + await assertNoDiagnostics(r''' +final class F {} + +class C extends F {} +'''); + } + + test_extends_class_classInterface() async { + await assertDiagnostics(r''' +interface class I {} + +class C extends I {} +''', [ + lint(28, 1), + ]); + } + + test_extends_class_classInterface_outsideLib_ok() async { + newFile('$testPackageLibPath/a.dart', r''' +interface class I {} +'''); + + await assertDiagnostics(r''' +import 'a.dart'; + +class C extends I {} +''', [ + error(CompileTimeErrorCode.INTERFACE_CLASS_EXTENDED_OUTSIDE_OF_LIBRARY, + 34, 1), + ]); + } + + test_extends_class_classInterface_reopened_ok() async { + await assertNoDiagnostics(r''' +import 'package:meta/meta.dart'; + +interface class I {} + +@reopen +class C extends I {} +'''); + } + + test_extends_class_classSealed_classInterface() async { + await assertDiagnostics(r''' +interface class I {} + +sealed class S extends I {} + +class C extends S {} +''', [ + lint(57, 1), + ]); + } + + test_extends_class_classSealed_mixinInterface() async { + await assertDiagnostics(r''' +interface class I {} + +sealed class S extends I {} + +class C extends S {} +''', [ + lint(57, 1), + ]); + } + + test_extends_classBase_classFinal() async { + await assertDiagnostics(r''' +final class F {} + +base class B extends F {} +''', [ + lint(29, 1), + ]); + } + + test_extends_classBase_classInterface() async { + await assertDiagnostics(r''' +interface class I {} + +base class B extends I {} +''', [ + lint(33, 1), + ]); + } + + test_extends_classBase_classSealed_classFinal() async { + await assertDiagnostics(r''' +final class F {} + +sealed class S extends F {} + +base class C extends S {} +''', [ + lint(58, 1), + ]); + } + + test_extends_classBase_classSealed_classInterface() async { + await assertDiagnostics(r''' +interface class I {} + +sealed class S extends I {} + +base class C extends S {} +''', [ + lint(62, 1), + ]); + } + + test_extends_classBase_classSealed_mixinInterface() async { + await assertDiagnostics(r''' +interface class I {} + +sealed class S extends I {} + +base class C extends S {} +''', [ + lint(62, 1), + ]); + } + + test_extends_classFinal_classInterface_ok() async { + await assertNoDiagnostics(r''' +interface class I {} + +final class C extends I {} +'''); + } + + test_with_class_mixinFinal_ok() async { + await assertNoDiagnostics(r''' +final mixin M {} + +class C with M {} +'''); + } + + test_with_class_mixinInterface() async { + await assertDiagnostics(r''' +interface mixin M {} + +class C with M {} +''', [ + lint(28, 1), + ]); + } + + test_with_classBase_mixinFinal() async { + await assertDiagnostics(r''' +final mixin M {} + +base class B with M {} +''', [ + lint(29, 1), + ]); + } + + test_with_classBase_mixinInterface() async { + await assertDiagnostics(r''' +interface mixin M {} + +base class B with M {} +''', [ + lint(33, 1), + ]); + } +} From fc053db63b87da7635a98de61c4c4b773a749555 Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 13:57:28 -0800 Subject: [PATCH 02/31] feedback --- lib/src/rules/implicit_reopen.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index b1146acf7..072b60acf 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -11,14 +11,16 @@ import '../analyzer.dart'; const _desc = r"Don't implicitly reopen classes or mixins"; -/// todo(pq): link out to the spec or dart.dev docs. +/// todo(pq): link out to (upcoming) dart.dev docs. const _details = r''' Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers, authors can control whether classes and mixins allow being implemented, extended, and/or mixed in from outside of the library where they're defined. In some cases, it's possible for an author to inadvertantly relax these controls -and implicitly "reopen" a class or mixin. This lint guards against that, -requiring such cases to be made explicit with the +and implicitly "reopen" a class or mixin. + +This lint guards against unintentionally reopening a type by requiring such +cases to be made explicit with the [`@reopen`](https://pub.dev/documentation/meta/latest/meta/reopen-constant.html) annotation in `package:meta`. @@ -50,7 +52,7 @@ class ImplicitReopen extends LintRule { static const LintCode code = LintCode('implicit_reopen', "The class '{0}' reopens '{1}' because it is not marked '{2}'", correctionMessage: - "Try marking {0} '{2}' or annotating it with '@reopen'"); + "Try marking '{0}' '{2}' or annotating it with '@reopen'"); ImplicitReopen() : super( From 74ec22c7bb7cdef8735e7764895bd55b493070eb Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 13:59:22 -0800 Subject: [PATCH 03/31] docs --- lib/src/rules/implicit_reopen.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 072b60acf..3b099735f 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -150,10 +150,10 @@ extension on InterfaceElement? { return self != null && self.isFinal; } - /// A sealed declaration D is considered final if it has a direct extends or - /// with superinterface which is final, or it has a direct superinterface - /// which is base as well as a direct extends or with superinterface which is - /// interface. + /// A sealed declaration `D` is considered final if it has a direct `extends` or + /// `with` superinterface which is `final`, or it has a direct superinterface + /// which is `base` as well as a direct `extends` or `with` superinterface + /// which is `interface`. bool get isInducedFinal { if (!isSealed) return false; @@ -170,8 +170,8 @@ extension on InterfaceElement? { return false; } - /// A sealed declaration D is considered interface if it has a direct extends - /// or with superinterface which is interface. + /// A sealed declaration `D` is considered interface if it has a direct + /// `extends` or `with` superinterface which is `interface`. bool get isInducedInterface { if (!isSealed) return false; From 9fa0a6fa9c09c0e41621f3398fa4e361f1b7c28f Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 15:03:12 -0800 Subject: [PATCH 04/31] class type aliases --- lib/src/rules/implicit_reopen.dart | 80 ++++++++++++++++++---------- test/rules/implicit_reopen_test.dart | 43 +++++++++++++-- 2 files changed, 90 insertions(+), 33 deletions(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 3b099735f..ccbb7cc94 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -50,9 +50,9 @@ class C extends I {} class ImplicitReopen extends LintRule { static const LintCode code = LintCode('implicit_reopen', - "The class '{0}' reopens '{1}' because it is not marked '{2}'", + "The {0} '{1}' reopens '{2}' because it is not marked '{3}'", correctionMessage: - "Try marking '{0}' '{2}' or annotating it with '@reopen'"); + "Try marking '{1}' '{3}' or annotating it with '@reopen'"); ImplicitReopen() : super( @@ -70,6 +70,7 @@ class ImplicitReopen extends LintRule { NodeLintRegistry registry, LinterContext context) { var visitor = _Visitor(this); registry.addClassDeclaration(this, visitor); + registry.addClassTypeAlias(this, visitor); } } @@ -78,68 +79,89 @@ class _Visitor extends SimpleAstVisitor { _Visitor(this.rule); - void reportLint( - NamedCompilationUnitMember member, { - required InterfaceElement target, - required InterfaceElement other, - required String reason, - }) { - rule.reportLintForToken(member.name, - arguments: [target.name, other.name, reason]); - } - - @override - void visitClassDeclaration(ClassDeclaration node) { - var classElement = node.declaredElement; - if (classElement == null) return; - if (classElement.hasReopen) return; - - var library = classElement.library; + void checkElement(InterfaceElement element, NamedCompilationUnitMember node, + {required String type}) { + var library = element.library; // | subtype | supertype | `extends`/`with` | // | _none_ or `base` | `interface` | lint | // | `base` | `final` | lint | - if (classElement.hasNoModifiers || classElement.isBase) { - var supertype = classElement.superElement; + if (element.hasNoModifiers || element.isBase) { + var supertype = element.superElement; if (supertype.library == library) { if (supertype.isInterface || supertype.isInducedInterface) { reportLint(node, - target: classElement, other: supertype!, reason: 'interface'); + target: element, + other: supertype!, + reason: 'interface', + type: type); return; } } - for (var m in classElement.mixins) { + for (var m in element.mixins) { var mixin = m.element; if (mixin.library != library) continue; if (mixin.isInterface || mixin.isInducedInterface) { reportLint(node, - target: classElement, other: mixin, reason: 'interface'); + target: element, other: mixin, reason: 'interface', type: type); return; } } } - if (classElement.isBase) { - var supertype = classElement.superElement; + if (element.isBase) { + var supertype = element.superElement; if (supertype.library == library) { if (supertype.isFinal || supertype.isInducedFinal) { reportLint(node, - target: classElement, other: supertype!, reason: 'final'); + target: element, other: supertype!, reason: 'final', type: type); return; } } - for (var m in classElement.mixins) { + for (var m in element.mixins) { var mixin = m.element; if (mixin.library != library) continue; if (mixin.isFinal || mixin.isInducedFinal) { - reportLint(node, target: classElement, other: mixin, reason: 'final'); + reportLint(node, + target: element, other: mixin, reason: 'final', type: type); return; } } } } + + void reportLint( + NamedCompilationUnitMember member, { + required String type, + required InterfaceElement target, + required InterfaceElement other, + required String reason, + }) { + rule.reportLintForToken(member.name, + arguments: [type, target.name, other.name, reason]); + } + + @override + visitClassTypeAlias(ClassTypeAlias node) { + print(node); + var classElement = node.declaredElement; + if (classElement == null) return; + if (classElement.hasReopen) return; + + checkElement(classElement, node, type: 'class'); + } + + @override + void visitClassDeclaration(ClassDeclaration node) { + var classElement = node.declaredElement; + if (classElement == null) return; + if (classElement.hasReopen) return; + + checkElement(classElement, node, type: 'class'); + } + } extension on InterfaceElement? { diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 68d7b1fed..5cd2fa67c 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -9,9 +9,32 @@ import '../rule_test_support.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(ImplicitReopenTest); + defineReflectiveTests(ImplicitReopenClassTypeAliasTest); }); } +@reflectiveTest +class ImplicitReopenClassTypeAliasTest extends LintRuleTest { + @override + bool get addMetaPackageDep => true; + + @override + List get experiments => ['class-modifiers', 'sealed-class']; + + @override + String get lintRule => 'implicit_reopen'; + + test_classBase_with_mixinFinal() async { + await assertDiagnostics(r''' +final mixin M {} + +base class C = Object with M; +''', [ + lint(29, 1), + ]); + } +} + @reflectiveTest class ImplicitReopenTest extends LintRuleTest { @override @@ -24,11 +47,17 @@ class ImplicitReopenTest extends LintRuleTest { String get lintRule => 'implicit_reopen'; test_extends_class_classFinal_ok() async { - await assertNoDiagnostics(r''' + await assertDiagnostics(r''' final class F {} class C extends F {} -'''); +''', [ + error( + CompileTimeErrorCode + .SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, + 24, + 1), + ]); } test_extends_class_classInterface() async { @@ -156,11 +185,17 @@ final class C extends I {} } test_with_class_mixinFinal_ok() async { - await assertNoDiagnostics(r''' + await assertDiagnostics(r''' final mixin M {} class C with M {} -'''); +''', [ + error( + CompileTimeErrorCode + .SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, + 24, + 1), + ]); } test_with_class_mixinInterface() async { From bb034729636485bbf8837927e1723016437ba0c6 Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 15:09:53 -0800 Subject: [PATCH 05/31] fmt --- lib/src/rules/implicit_reopen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index ccbb7cc94..d754da9a7 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -161,7 +161,6 @@ class _Visitor extends SimpleAstVisitor { checkElement(classElement, node, type: 'class'); } - } extension on InterfaceElement? { From 4e5f345f15271ae1a7c2622ddd89a2410dc34bbb Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 15:14:45 -0800 Subject: [PATCH 06/31] fix description --- lib/src/rules/implicit_reopen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index d754da9a7..e4364b70f 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -9,7 +9,7 @@ import 'package:analyzer/dart/element/type.dart'; import '../analyzer.dart'; -const _desc = r"Don't implicitly reopen classes or mixins"; +const _desc = r"Don't implicitly reopen classes or mixins."; /// todo(pq): link out to (upcoming) dart.dev docs. const _details = r''' From 9c83e393a44515598b77581fcb14b486397fa674 Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 15:20:05 -0800 Subject: [PATCH 07/31] hoist --- lib/src/rules/implicit_reopen.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index e4364b70f..bc8d463b1 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -144,8 +144,7 @@ class _Visitor extends SimpleAstVisitor { } @override - visitClassTypeAlias(ClassTypeAlias node) { - print(node); + void visitClassDeclaration(ClassDeclaration node) { var classElement = node.declaredElement; if (classElement == null) return; if (classElement.hasReopen) return; @@ -154,7 +153,8 @@ class _Visitor extends SimpleAstVisitor { } @override - void visitClassDeclaration(ClassDeclaration node) { + visitClassTypeAlias(ClassTypeAlias node) { + print(node); var classElement = node.declaredElement; if (classElement == null) return; if (classElement.hasReopen) return; @@ -179,11 +179,11 @@ extension on InterfaceElement? { if (!isSealed) return false; if (superElement.isFinal) return true; + if (!superElement.isInterface) return false; if (mixins.any((m) => m.element.isFinal)) return true; for (var i in interfaces) { if (i.element.isBase) { - if (superElement.isInterface) return true; if (mixins.any((m) => m.element.isInterface)) return true; } } From 1a1c5e919ef0df1d721a1fd1e267951ef643e775 Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 1 Mar 2023 15:47:00 -0800 Subject: [PATCH 08/31] -- --- lib/src/rules/implicit_reopen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index bc8d463b1..bc5b03a13 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -154,7 +154,6 @@ class _Visitor extends SimpleAstVisitor { @override visitClassTypeAlias(ClassTypeAlias node) { - print(node); var classElement = node.declaredElement; if (classElement == null) return; if (classElement.hasReopen) return; From 67848e31e98eb80a5a25ff1f9fb60d83eff59365 Mon Sep 17 00:00:00 2001 From: pq Date: Mon, 20 Mar 2023 15:35:38 -0700 Subject: [PATCH 09/31] rebase --- test/rules/implicit_reopen_test.dart | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 5cd2fa67c..3aedfe3d2 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -14,13 +14,11 @@ main() { } @reflectiveTest -class ImplicitReopenClassTypeAliasTest extends LintRuleTest { +class ImplicitReopenClassTypeAliasTest extends LintRuleTest + with LanguageVersion300Mixin { @override bool get addMetaPackageDep => true; - @override - List get experiments => ['class-modifiers', 'sealed-class']; - @override String get lintRule => 'implicit_reopen'; @@ -36,13 +34,10 @@ base class C = Object with M; } @reflectiveTest -class ImplicitReopenTest extends LintRuleTest { +class ImplicitReopenTest extends LintRuleTest with LanguageVersion300Mixin { @override bool get addMetaPackageDep => true; - @override - List get experiments => ['class-modifiers', 'sealed-class']; - @override String get lintRule => 'implicit_reopen'; @@ -52,11 +47,8 @@ final class F {} class C extends F {} ''', [ - error( - CompileTimeErrorCode - .SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, - 24, - 1), + error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, + 24, 1), ]); } @@ -190,11 +182,8 @@ final mixin M {} class C with M {} ''', [ - error( - CompileTimeErrorCode - .SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, - 24, - 1), + error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, + 24, 1), ]); } From 309bd6e8d74acd723a96cf2fb502129302a0dbbe Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 21 Mar 2023 13:21:22 -0700 Subject: [PATCH 10/31] revisit --- lib/src/rules/implicit_reopen.dart | 147 +++---------- test/rules/implicit_reopen_test.dart | 307 +++++++++++++++++---------- 2 files changed, 231 insertions(+), 223 deletions(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index bc5b03a13..692935e29 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -5,7 +5,6 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; import '../analyzer.dart'; @@ -70,7 +69,6 @@ class ImplicitReopen extends LintRule { NodeLintRegistry registry, LinterContext context) { var visitor = _Visitor(this); registry.addClassDeclaration(this, visitor); - registry.addClassTypeAlias(this, visitor); } } @@ -79,55 +77,37 @@ class _Visitor extends SimpleAstVisitor { _Visitor(this.rule); - void checkElement(InterfaceElement element, NamedCompilationUnitMember node, + void checkElement(InterfaceElement? element, NamedCompilationUnitMember node, {required String type}) { - var library = element.library; - - // | subtype | supertype | `extends`/`with` | - // | _none_ or `base` | `interface` | lint | - // | `base` | `final` | lint | - if (element.hasNoModifiers || element.isBase) { - var supertype = element.superElement; - if (supertype.library == library) { - if (supertype.isInterface || supertype.isInducedInterface) { - reportLint(node, - target: element, - other: supertype!, - reason: 'interface', - type: type); - return; - } - } + if (element == null) return; + if (element.hasReopen) return; + if (element.isSealed) return; - for (var m in element.mixins) { - var mixin = m.element; - if (mixin.library != library) continue; - if (mixin.isInterface || mixin.isInducedInterface) { - reportLint(node, - target: element, other: mixin, reason: 'interface', type: type); - return; - } - } - } + var library = element.library; + var supertype = element.superElement; + if (supertype.library != library) return; if (element.isBase) { - var supertype = element.superElement; - if (supertype.library == library) { - if (supertype.isFinal || supertype.isInducedFinal) { - reportLint(node, - target: element, other: supertype!, reason: 'final', type: type); - return; - } + if (supertype.isFinal) { + reportLint(node, + target: element, other: supertype!, reason: 'final', type: type); + return; + } else if (supertype.isInterface) { + reportLint(node, + target: element, + other: supertype!, + reason: 'interface', + type: type); + return; } - - for (var m in element.mixins) { - var mixin = m.element; - if (mixin.library != library) continue; - if (mixin.isFinal || mixin.isInducedFinal) { - reportLint(node, - target: element, other: mixin, reason: 'final', type: type); - return; - } + } else if (element.hasNoModifiers) { + if (supertype.isInterface) { + reportLint(node, + target: element, + other: supertype!, + reason: 'interface', + type: type); + return; } } } @@ -145,80 +125,11 @@ class _Visitor extends SimpleAstVisitor { @override void visitClassDeclaration(ClassDeclaration node) { - var classElement = node.declaredElement; - if (classElement == null) return; - if (classElement.hasReopen) return; - - checkElement(classElement, node, type: 'class'); - } - - @override - visitClassTypeAlias(ClassTypeAlias node) { - var classElement = node.declaredElement; - if (classElement == null) return; - if (classElement.hasReopen) return; - - checkElement(classElement, node, type: 'class'); + checkElement(node.declaredElement, node, type: 'class'); } } extension on InterfaceElement? { - List get interfaces => this?.interfaces ?? []; - - bool get isFinal { - var self = this; - return self != null && self.isFinal; - } - - /// A sealed declaration `D` is considered final if it has a direct `extends` or - /// `with` superinterface which is `final`, or it has a direct superinterface - /// which is `base` as well as a direct `extends` or `with` superinterface - /// which is `interface`. - bool get isInducedFinal { - if (!isSealed) return false; - - if (superElement.isFinal) return true; - if (!superElement.isInterface) return false; - if (mixins.any((m) => m.element.isFinal)) return true; - - for (var i in interfaces) { - if (i.element.isBase) { - if (mixins.any((m) => m.element.isInterface)) return true; - } - } - - return false; - } - - /// A sealed declaration `D` is considered interface if it has a direct - /// `extends` or `with` superinterface which is `interface`. - bool get isInducedInterface { - if (!isSealed) return false; - - if (superElement.isInterface) return true; - if (mixins.any((m) => m.element.isInterface)) return true; - - return false; - } - - bool get isInterface { - var self = this; - return self != null && self.isInterface; - } - - bool get isSealed { - var self = this; - return self != null && self.isSealed; - } - - LibraryElement? get library => this?.library; - - List get mixins => this?.mixins ?? []; - - InterfaceElement? get superElement => this?.supertype?.element; -} - -extension on InterfaceElement { bool get hasNoModifiers => !isInterface && !isBase && !isSealed && !isFinal; bool get isBase { @@ -248,4 +159,8 @@ extension on InterfaceElement { if (self is MixinElement) return self.isSealed; return false; } + + LibraryElement? get library => this?.library; + + InterfaceElement? get superElement => this?.supertype?.element; } diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 3aedfe3d2..69f4e0398 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -9,12 +9,12 @@ import '../rule_test_support.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(ImplicitReopenTest); - defineReflectiveTests(ImplicitReopenClassTypeAliasTest); + defineReflectiveTests(ImplicitReopenInducedModifierTest); }); } @reflectiveTest -class ImplicitReopenClassTypeAliasTest extends LintRuleTest +class ImplicitReopenInducedModifierTest extends LintRuleTest with LanguageVersion300Mixin { @override bool get addMetaPackageDep => true; @@ -22,198 +22,291 @@ class ImplicitReopenClassTypeAliasTest extends LintRuleTest @override String get lintRule => 'implicit_reopen'; - test_classBase_with_mixinFinal() async { + test_class_classSealed_classFinal_ok() async { await assertDiagnostics(r''' -final mixin M {} - -base class C = Object with M; +final class F {} +sealed class S extends F {} +class C extends S {} ''', [ - lint(29, 1), + // No lint. + error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, + 51, 1), ]); } -} - -@reflectiveTest -class ImplicitReopenTest extends LintRuleTest with LanguageVersion300Mixin { - @override - bool get addMetaPackageDep => true; - - @override - String get lintRule => 'implicit_reopen'; - test_extends_class_classFinal_ok() async { + test_inducedFinal() async { await assertDiagnostics(r''' final class F {} - -class C extends F {} +sealed class S extends F {} +base class C extends S {} ''', [ - error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, - 24, 1), + lint(56, 1), ]); } - - test_extends_class_classInterface() async { + + test_inducedFinal_base_interface() async { await assertDiagnostics(r''' -interface class I {} - -class C extends I {} +base class C {} +interface class D {} +sealed class E extends D implements C {} +base class B extends E {} ''', [ - lint(28, 1), + lint(89, 1), ]); } - test_extends_class_classInterface_outsideLib_ok() async { - newFile('$testPackageLibPath/a.dart', r''' -interface class I {} -'''); - + test_inducedFinal_baseMixin_interface() async { await assertDiagnostics(r''' -import 'a.dart'; - -class C extends I {} +interface class D {} +base mixin G {} +sealed class H extends D with G {} +base class B extends H {} ''', [ - error(CompileTimeErrorCode.INTERFACE_CLASS_EXTENDED_OUTSIDE_OF_LIBRARY, - 34, 1), + lint(83, 1), ]); } - test_extends_class_classInterface_reopened_ok() async { - await assertNoDiagnostics(r''' -import 'package:meta/meta.dart'; - -interface class I {} - -@reopen -class C extends I {} -'''); + test_inducedFinal_mixin_finalClass() async { + await assertDiagnostics(r''' +final class S {} +mixin M {} +sealed class A extends S with M {} +base class B extends A {} +''', [ + lint(74, 1), + ]); } - test_extends_class_classSealed_classInterface() async { + test_inducedInterface() async { await assertDiagnostics(r''' interface class I {} - sealed class S extends I {} - class C extends S {} ''', [ - lint(57, 1), + lint(55, 1), ]); } - test_extends_class_classSealed_mixinInterface() async { + test_inducedInterface_base() async { await assertDiagnostics(r''' interface class I {} - sealed class S extends I {} - -class C extends S {} +base class C extends S {} ''', [ - lint(57, 1), + lint(60, 1), ]); } - test_extends_classBase_classFinal() async { + test_inducedInterface_base_mixin_interface() async { await assertDiagnostics(r''' -final class F {} - -base class B extends F {} +interface class S {} +mixin M {} +sealed class A extends S with M {} +base class C extends A {} ''', [ - lint(29, 1), + lint(78, 1), ]); } - test_extends_classBase_classInterface() async { + test_inducedInterface_mixin_interface() async { await assertDiagnostics(r''' -interface class I {} - -base class B extends I {} +interface class S {} +mixin M {} +sealed class A extends S with M {} +class C extends A {} ''', [ - lint(33, 1), + lint(73, 1), ]); } +} + +@reflectiveTest +class ImplicitReopenTest extends LintRuleTest with LanguageVersion300Mixin { + @override + bool get addMetaPackageDep => true; - test_extends_classBase_classSealed_classFinal() async { + @override + String get lintRule => 'implicit_reopen'; + + test_class_classFinal_ok() async { await assertDiagnostics(r''' final class F {} -sealed class S extends F {} - -base class C extends S {} +class C extends F {} ''', [ - lint(58, 1), + error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, + 24, 1), ]); } - test_extends_classBase_classSealed_classInterface() async { + test_class_classInterface() async { await assertDiagnostics(r''' interface class I {} -sealed class S extends I {} - -base class C extends S {} +class C extends I {} ''', [ - lint(62, 1), + lint(28, 1), ]); } - test_extends_classBase_classSealed_mixinInterface() async { - await assertDiagnostics(r''' + test_class_classInterface_outsideLib_ok() async { + newFile('$testPackageLibPath/a.dart', r''' interface class I {} +'''); -sealed class S extends I {} + await assertDiagnostics(r''' +import 'a.dart'; -base class C extends S {} +class C extends I {} ''', [ - lint(62, 1), + error(CompileTimeErrorCode.INTERFACE_CLASS_EXTENDED_OUTSIDE_OF_LIBRARY, + 34, 1), ]); } - test_extends_classFinal_classInterface_ok() async { + test_class_classInterface_reopened_ok() async { await assertNoDiagnostics(r''' +import 'package:meta/meta.dart'; + interface class I {} -final class C extends I {} +@reopen +class C extends I {} '''); } - test_with_class_mixinFinal_ok() async { + test_classBase_classFinal() async { await assertDiagnostics(r''' -final mixin M {} +final class F {} -class C with M {} +base class B extends F {} ''', [ - error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, - 24, 1), + lint(29, 1), ]); } - test_with_class_mixinInterface() async { + test_classBase_classInterface() async { await assertDiagnostics(r''' -interface mixin M {} +interface class I {} -class C with M {} +base class B extends I {} ''', [ - lint(28, 1), + lint(33, 1), ]); } - test_with_classBase_mixinFinal() async { - await assertDiagnostics(r''' -final mixin M {} + test_classFinal_classInterface_ok() async { + await assertNoDiagnostics(r''' +interface class I {} -base class B with M {} -''', [ - lint(29, 1), - ]); +final class C extends I {} +'''); } - test_with_classBase_mixinInterface() async { - await assertDiagnostics(r''' -interface mixin M {} - -base class B with M {} -''', [ - lint(33, 1), - ]); - } +// +// test_extends_class_classSealed_classInterface() async { +// await assertDiagnostics(r''' +// interface class I {} +// +// sealed class S extends I {} +// +// class C extends S {} +// ''', [ +// lint(57, 1), +// ]); +// } +// +// test_extends_class_classSealed_mixinInterface() async { +// await assertDiagnostics(r''' +// interface class I {} +// +// sealed class S extends I {} +// +// class C extends S {} +// ''', [ +// lint(57, 1), +// ]); +// } +// +// + +// +// test_extends_classBase_classSealed_classFinal() async { +// await assertDiagnostics(r''' +// final class F {} +// +// sealed class S extends F {} +// +// base class C extends S {} +// ''', [ +// lint(58, 1), +// ]); +// } +// +// test_extends_classBase_classSealed_classInterface() async { +// await assertDiagnostics(r''' +// interface class I {} +// +// sealed class S extends I {} +// +// base class C extends S {} +// ''', [ +// lint(62, 1), +// ]); +// } +// +// test_extends_classBase_classSealed_mixinInterface() async { +// await assertDiagnostics(r''' +// interface class I {} +// +// sealed class S extends I {} +// +// base class C extends S {} +// ''', [ +// lint(62, 1), +// ]); +// } +// + +// +// test_with_class_mixinFinal_ok() async { +// await assertDiagnostics(r''' +// final mixin M {} +// +// class C with M {} +// ''', [ +// error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, +// 24, 1), +// ]); +// } +// +// test_with_class_mixinInterface() async { +// await assertDiagnostics(r''' +// interface mixin M {} +// +// class C with M {} +// ''', [ +// lint(28, 1), +// ]); +// } +// +// test_with_classBase_mixinFinal() async { +// await assertDiagnostics(r''' +// final mixin M {} +// +// base class B with M {} +// ''', [ +// lint(29, 1), +// ]); +// } +// +// test_with_classBase_mixinInterface() async { +// await assertDiagnostics(r''' +// interface mixin M {} +// +// base class B with M {} +// ''', [ +// lint(33, 1), +// ]); +// } } From 809ec9f212414e939f7d47f8572ba3d088bc97a8 Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 21 Mar 2023 13:22:18 -0700 Subject: [PATCH 11/31] ++ --- lib/src/rules/implicit_reopen.dart | 1 + test/rules/implicit_reopen_test.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 692935e29..5572cf41b 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -11,6 +11,7 @@ import '../analyzer.dart'; const _desc = r"Don't implicitly reopen classes or mixins."; /// todo(pq): link out to (upcoming) dart.dev docs. +/// https://github.com/dart-lang/site-www/issues/4497 const _details = r''' Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers, authors can control whether classes and mixins allow being implemented, diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 69f4e0398..054e199ef 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -43,7 +43,7 @@ base class C extends S {} lint(56, 1), ]); } - + test_inducedFinal_base_interface() async { await assertDiagnostics(r''' base class C {} From 21926cacf6538ab96289c008af96407d766f0faa Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 21 Mar 2023 13:26:27 -0700 Subject: [PATCH 12/31] ++ --- test/rules/implicit_reopen_test.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 054e199ef..82cf98d49 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -118,6 +118,17 @@ class C extends A {} lint(73, 1), ]); } + + test_inducedInterface_twoLevels() async { + await assertDiagnostics(r''' +interface class I {} +sealed class S extends I {} +sealed class S2 extends S {} +class C extends S2 {} +''', [ + lint(84, 1), + ]); + } } @reflectiveTest From cb6618ad5032d6b6439b9946df46a32a471fedad Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 21 Mar 2023 13:29:57 -0700 Subject: [PATCH 13/31] -- --- test/rules/implicit_reopen_test.dart | 107 --------------------------- 1 file changed, 107 deletions(-) diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 82cf98d49..9ecfd5372 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -213,111 +213,4 @@ interface class I {} final class C extends I {} '''); } - -// -// test_extends_class_classSealed_classInterface() async { -// await assertDiagnostics(r''' -// interface class I {} -// -// sealed class S extends I {} -// -// class C extends S {} -// ''', [ -// lint(57, 1), -// ]); -// } -// -// test_extends_class_classSealed_mixinInterface() async { -// await assertDiagnostics(r''' -// interface class I {} -// -// sealed class S extends I {} -// -// class C extends S {} -// ''', [ -// lint(57, 1), -// ]); -// } -// -// - -// -// test_extends_classBase_classSealed_classFinal() async { -// await assertDiagnostics(r''' -// final class F {} -// -// sealed class S extends F {} -// -// base class C extends S {} -// ''', [ -// lint(58, 1), -// ]); -// } -// -// test_extends_classBase_classSealed_classInterface() async { -// await assertDiagnostics(r''' -// interface class I {} -// -// sealed class S extends I {} -// -// base class C extends S {} -// ''', [ -// lint(62, 1), -// ]); -// } -// -// test_extends_classBase_classSealed_mixinInterface() async { -// await assertDiagnostics(r''' -// interface class I {} -// -// sealed class S extends I {} -// -// base class C extends S {} -// ''', [ -// lint(62, 1), -// ]); -// } -// - -// -// test_with_class_mixinFinal_ok() async { -// await assertDiagnostics(r''' -// final mixin M {} -// -// class C with M {} -// ''', [ -// error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, -// 24, 1), -// ]); -// } -// -// test_with_class_mixinInterface() async { -// await assertDiagnostics(r''' -// interface mixin M {} -// -// class C with M {} -// ''', [ -// lint(28, 1), -// ]); -// } -// -// test_with_classBase_mixinFinal() async { -// await assertDiagnostics(r''' -// final mixin M {} -// -// base class B with M {} -// ''', [ -// lint(29, 1), -// ]); -// } -// -// test_with_classBase_mixinInterface() async { -// await assertDiagnostics(r''' -// interface mixin M {} -// -// base class B with M {} -// ''', [ -// lint(33, 1), -// ]); -// } } From 87f6d09c3a58d675ec9756b4960fd8d176ebce0d Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 21 Mar 2023 15:23:11 -0700 Subject: [PATCH 14/31] ++ --- lib/src/rules/implicit_reopen.dart | 10 +++++++--- test/rules/implicit_reopen_test.dart | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 5572cf41b..294fb728d 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -83,6 +83,7 @@ class _Visitor extends SimpleAstVisitor { if (element == null) return; if (element.hasReopen) return; if (element.isSealed) return; + if (element.isMixinClass) return; var library = element.library; var supertype = element.superElement; @@ -143,21 +144,24 @@ extension on InterfaceElement? { bool get isFinal { var self = this; if (self is ClassElement) return self.isFinal; - if (self is MixinElement) return self.isFinal; return false; } bool get isInterface { var self = this; if (self is ClassElement) return self.isInterface; - if (self is MixinElement) return self.isInterface; return false; } bool get isSealed { var self = this; if (self is ClassElement) return self.isSealed; - if (self is MixinElement) return self.isSealed; + return false; + } + + bool get isMixinClass { + var self = this; + if (self is ClassElement) return self.isMixinClass; return false; } diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 9ecfd5372..a185860b0 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -213,4 +213,27 @@ interface class I {} final class C extends I {} '''); } + + test_classMixin_classInterface_ok() async { + await assertDiagnostics(r''' +interface class I {} + +mixin class M extends I {} +''', [ + // No lint. + error(CompileTimeErrorCode.MIXIN_CLASS_DECLARATION_EXTENDS_NOT_OBJECT, 44, + 1), + ]); + } + + test_mixin_classInterface_ok() async { + await assertDiagnostics(r''' +interface class I {} + +mixin M extends I {} +''', [ + // No lint. + error(ParserErrorCode.EXPECTED_INSTEAD, 30, 7), + ]); + } } From a8938cb526224e478479a0f399a99ad776f36cc4 Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 21 Mar 2023 15:28:51 -0700 Subject: [PATCH 15/31] + test --- test/rules/implicit_reopen_test.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index a185860b0..f50857dde 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -66,6 +66,19 @@ base class B extends H {} ]); } + test_inducedFinal_interface_base_ok() async { + await assertDiagnostics(r''' +interface class S {} +base class I {} +sealed class A extends S implements I {} +class C extends A {} +''', [ + // No lint. + error(CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED, + 84, 1), + ]); + } + test_inducedFinal_mixin_finalClass() async { await assertDiagnostics(r''' final class S {} From 148c6ea7d2729120d0dd971658613b50613f4013 Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 21 Mar 2023 16:02:38 -0700 Subject: [PATCH 16/31] // --- test/rules/implicit_reopen_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index f50857dde..34d1c8d0d 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -158,6 +158,7 @@ final class F {} class C extends F {} ''', [ + // No lint. error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, 24, 1), ]); @@ -183,6 +184,7 @@ import 'a.dart'; class C extends I {} ''', [ + // No lint. error(CompileTimeErrorCode.INTERFACE_CLASS_EXTENDED_OUTSIDE_OF_LIBRARY, 34, 1), ]); From 6937848e1c5bd651c237891aa182cab98b6d5aed Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 21 Mar 2023 07:23:20 -0700 Subject: [PATCH 17/31] Fix #4165 by visiting SwitchPatternCase (#4169) --- lib/src/rules/use_build_context_synchronously.dart | 7 +++++++ test_data/rules/use_build_context_synchronously.dart | 8 ++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/src/rules/use_build_context_synchronously.dart b/lib/src/rules/use_build_context_synchronously.dart index e49a4b4ff..5db446bbc 100644 --- a/lib/src/rules/use_build_context_synchronously.dart +++ b/lib/src/rules/use_build_context_synchronously.dart @@ -167,6 +167,13 @@ class _Visitor extends SimpleAstVisitor { return; } } else if (parent is SwitchCase) { + // Necessary for Dart 2.19 code. + var keepChecking = checkStatements(child, parent.statements); + if (!keepChecking) { + return; + } + } else if (parent is SwitchPatternCase) { + // Necessary for Dart 3.0 code. var keepChecking = checkStatements(child, parent.statements); if (!keepChecking) { return; diff --git a/test_data/rules/use_build_context_synchronously.dart b/test_data/rules/use_build_context_synchronously.dart index acad3f221..b54d379cc 100644 --- a/test_data/rules/use_build_context_synchronously.dart +++ b/test_data/rules/use_build_context_synchronously.dart @@ -249,9 +249,7 @@ class _MyState extends State { switch (1) { case 1: if (!mounted) return; - // TODO(https://github.com/dart-lang/linter/issues/4165): This should - // be OK. - await Navigator.of(context).pushNamed('routeName'); // LINT + await Navigator.of(context).pushNamed('routeName'); // OK break; } } @@ -350,9 +348,7 @@ void topLevel5(BuildContext context) async { if (!mounted) { break; } - // TODO(https://github.com/dart-lang/linter/issues/4165): This should - // be OK. - Navigator.of(context).pushNamed('routeName2'); // LINT + Navigator.of(context).pushNamed('routeName222'); // OK break; default: //nothing. } From 895fdc67c6317ecb8fe596e281e8fdb69509c298 Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Tue, 21 Mar 2023 14:26:28 -0500 Subject: [PATCH 18/31] Use deps.dev for OpenSSF scorecard results link (#4184) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b5a36818..a72f32611 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ analysis server and the `dart analyze` command in the [Dart command-line tool][d [![Coverage Status](https://coveralls.io/repos/dart-lang/linter/badge.svg)](https://coveralls.io/r/dart-lang/linter) [![Pub](https://img.shields.io/pub/v/linter.svg)](https://pub.dev/packages/linter) [![package publisher](https://img.shields.io/pub/publisher/linter.svg)](https://pub.dev/packages/linter/publisher) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/dart-lang/linter/badge)](https://api.securityscorecards.dev/projects/github.com/dart-lang/linter) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/dart-lang/linter/badge)](https://deps.dev/project/github/dart-lang%2Flinter) ## Installing From 7e837885477daa019fd2e1242ebf4abad8a16366 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Tue, 21 Mar 2023 12:30:36 -0700 Subject: [PATCH 19/31] prefer_final_parameters tests (#4182) * prefer_final_parameters tests * -- --- test/rules/prefer_final_parameters_test.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/rules/prefer_final_parameters_test.dart b/test/rules/prefer_final_parameters_test.dart index 172671642..8b0d52811 100644 --- a/test/rules/prefer_final_parameters_test.dart +++ b/test/rules/prefer_final_parameters_test.dart @@ -53,13 +53,22 @@ class PreferFinalParametersTestLanguage300 extends LintRuleTest String get lintRule => 'prefer_final_parameters'; @FailingTest(issue: 'https://github.com/dart-lang/linter/issues/4156') - test_recordPattern_destructured() async { + test_listPattern_destructured() async { await assertNoDiagnostics(''' void f(int p) { - var list = [1, 2, 3]; - var [_, p, _] = list; + [_, p, _] = [1, 2, 3]; print(p); } +'''); + } + + @FailingTest(issue: 'https://github.com/dart-lang/linter/issues/4156') + test_recordPattern_destructured() async { + await assertNoDiagnostics(r''' +void f(int a, int b) { + (a, b) = (1, 2); + print('$a$b'); +} '''); } } From 8fba507edefe7b211c2602cfa26208b1df0aa1a0 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 21 Mar 2023 15:10:47 -0700 Subject: [PATCH 20/31] switch to the latest published version of the setup-dart action (#4186) --- .github/workflows/build.yaml | 2 +- .github/workflows/gh-pages.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 42b09ab2a..2daf82181 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@929ed5f8bae55086c6fac456ba5acb9763c9e3e2 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: dev diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 9416b2244..3034f5e7b 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -19,7 +19,7 @@ jobs: - name: Checkout repository uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - name: Setup Dart - uses: dart-lang/setup-dart@929ed5f8bae55086c6fac456ba5acb9763c9e3e2 + uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: stable - name: Get dependencies From d390f9d51a6352dcf5ee01ac76fd0893feb5d688 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Tue, 21 Mar 2023 15:13:10 -0700 Subject: [PATCH 21/31] test cleanup (#4185) --- test/rules/prefer_final_parameters_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/rules/prefer_final_parameters_test.dart b/test/rules/prefer_final_parameters_test.dart index 8b0d52811..166ad71c7 100644 --- a/test/rules/prefer_final_parameters_test.dart +++ b/test/rules/prefer_final_parameters_test.dart @@ -57,7 +57,6 @@ class PreferFinalParametersTestLanguage300 extends LintRuleTest await assertNoDiagnostics(''' void f(int p) { [_, p, _] = [1, 2, 3]; - print(p); } '''); } @@ -67,7 +66,6 @@ void f(int p) { await assertNoDiagnostics(r''' void f(int a, int b) { (a, b) = (1, 2); - print('$a$b'); } '''); } From 1bb7017fbb9a1b5f20cf2b3f6e9e95481b72b5a7 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 21 Mar 2023 20:12:40 -0700 Subject: [PATCH 22/31] Support analyzer 5.9.0 (#4188) --- pubspec.yaml | 2 +- test/rule_test_support.dart | 19 ++++++++++----- test/rules/no_duplicate_case_values_test.dart | 23 ++++++++++++++----- test/rules/prefer_final_parameters_test.dart | 2 -- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 4d1a722bc..56d5485df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ environment: sdk: '>=2.17.0 <3.0.0' dependencies: - analyzer: ^5.8.0 + analyzer: ^5.9.0 args: ^2.1.0 collection: ^1.15.0 http: ^0.13.0 diff --git a/test/rule_test_support.dart b/test/rule_test_support.dart index 902f6c7e2..bbfdbee5a 100644 --- a/test/rule_test_support.dart +++ b/test/rule_test_support.dart @@ -221,12 +221,19 @@ abstract class LintRuleTest extends PubPackageResolutionTest { if (dumpAstOnFailures) { buffer.writeln(); buffer.writeln(); - var astSink = CollectingSink(); - StringSpelunker(result.unit.toSource(), - sink: astSink, featureSet: result.unit.featureSet) - .spelunk(); - buffer.write(astSink.buffer); - buffer.writeln(); + try { + var astSink = CollectingSink(); + + StringSpelunker(result.unit.toSource(), + sink: astSink, featureSet: result.unit.featureSet) + .spelunk(); + buffer.write(astSink.buffer); + buffer.writeln(); + // I hereby choose to catch this type. + // ignore: avoid_catching_errors + } on ArgumentError catch (_) { + // Perhaps we encountered a parsing error while spelunking. + } } errors.sort((first, second) => first.offset.compareTo(second.offset)); diff --git a/test/rules/no_duplicate_case_values_test.dart b/test/rules/no_duplicate_case_values_test.dart index 04031d1ab..6ebee0198 100644 --- a/test/rules/no_duplicate_case_values_test.dart +++ b/test/rules/no_duplicate_case_values_test.dart @@ -115,7 +115,7 @@ void switchString() { class NoDuplicateCaseValuesTestLanguage300 extends BaseNoDuplicateCaseValuesTest with LanguageVersion300Mixin { test_duplicateConstClassValue_ok() async { - await assertNoDiagnostics(r''' + await assertDiagnostics(r''' class ConstClass { final int v; const ConstClass(this.v); @@ -132,11 +132,13 @@ void switchConstClass() { default: } } -'''); +''', [ + error(HintCode.UNREACHABLE_SWITCH_CASE, 239, 4), + ]); } test_duplicateEnumValue_ok() async { - await assertNoDiagnostics(r''' + await assertDiagnostics(r''' enum E { one, two, @@ -154,11 +156,14 @@ void switchEnum() { default: } } -'''); +''', [ + // No lint. + error(HintCode.UNREACHABLE_SWITCH_CASE, 144, 4), + ]); } test_duplicateIntConstant_ok() async { - await assertNoDiagnostics(r''' + await assertDiagnostics(r''' void switchInt() { const int A = 1; int v = 5; @@ -172,7 +177,11 @@ void switchInt() { default: } } -'''); +''', [ + // No lint. + error(HintCode.UNREACHABLE_SWITCH_CASE, 95, 4), + error(HintCode.UNREACHABLE_SWITCH_CASE, 107, 4), + ]); } test_duplicateStringConstant_ok() async { @@ -192,7 +201,9 @@ void switchString() { } ''', [ // No lint. + error(HintCode.UNREACHABLE_SWITCH_CASE, 115, 4), error(ParserErrorCode.INVALID_CONSTANT_PATTERN_BINARY, 122, 1), + error(HintCode.UNREACHABLE_SWITCH_CASE, 131, 4), error(ParserErrorCode.INVALID_CONSTANT_PATTERN_BINARY, 153, 1), ]); } diff --git a/test/rules/prefer_final_parameters_test.dart b/test/rules/prefer_final_parameters_test.dart index 166ad71c7..b059cb499 100644 --- a/test/rules/prefer_final_parameters_test.dart +++ b/test/rules/prefer_final_parameters_test.dart @@ -52,7 +52,6 @@ class PreferFinalParametersTestLanguage300 extends LintRuleTest @override String get lintRule => 'prefer_final_parameters'; - @FailingTest(issue: 'https://github.com/dart-lang/linter/issues/4156') test_listPattern_destructured() async { await assertNoDiagnostics(''' void f(int p) { @@ -61,7 +60,6 @@ void f(int p) { '''); } - @FailingTest(issue: 'https://github.com/dart-lang/linter/issues/4156') test_recordPattern_destructured() async { await assertNoDiagnostics(r''' void f(int a, int b) { From ed9bafa1ebcf2cf10d693434a6f44d6b275413b3 Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 22 Mar 2023 10:59:49 -0700 Subject: [PATCH 23/31] - mixin --- lib/src/rules/implicit_reopen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 294fb728d..9c2ac732d 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -17,7 +17,7 @@ Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers, authors can control whether classes and mixins allow being implemented, extended, and/or mixed in from outside of the library where they're defined. In some cases, it's possible for an author to inadvertantly relax these controls -and implicitly "reopen" a class or mixin. +and implicitly "reopen" a class. This lint guards against unintentionally reopening a type by requiring such cases to be made explicit with the From f00b3daa0b962c373f371e1de89b20fec525ef8a Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 22 Mar 2023 11:01:03 -0700 Subject: [PATCH 24/31] rename --- test/rules/implicit_reopen_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 34d1c8d0d..2e072c31a 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -22,7 +22,7 @@ class ImplicitReopenInducedModifierTest extends LintRuleTest @override String get lintRule => 'implicit_reopen'; - test_class_classSealed_classFinal_ok() async { + test_subtypingFinalError_ok() async { await assertDiagnostics(r''' final class F {} sealed class S extends F {} From f2b3818a81db3015f56091cdfe7ee240a256e576 Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 22 Mar 2023 11:01:50 -0700 Subject: [PATCH 25/31] rename --- test/rules/implicit_reopen_test.dart | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/rules/implicit_reopen_test.dart b/test/rules/implicit_reopen_test.dart index 2e072c31a..6acf7d4ec 100644 --- a/test/rules/implicit_reopen_test.dart +++ b/test/rules/implicit_reopen_test.dart @@ -22,18 +22,6 @@ class ImplicitReopenInducedModifierTest extends LintRuleTest @override String get lintRule => 'implicit_reopen'; - test_subtypingFinalError_ok() async { - await assertDiagnostics(r''' -final class F {} -sealed class S extends F {} -class C extends S {} -''', [ - // No lint. - error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, - 51, 1), - ]); - } - test_inducedFinal() async { await assertDiagnostics(r''' final class F {} @@ -142,6 +130,18 @@ class C extends S2 {} lint(84, 1), ]); } + + test_subtypingFinalError_ok() async { + await assertDiagnostics(r''' +final class F {} +sealed class S extends F {} +class C extends S {} +''', [ + // No lint. + error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED, + 51, 1), + ]); + } } @reflectiveTest From d0e228cd589f24dba07e3034e90bb1ba317a82c9 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 22 Mar 2023 13:15:54 -0700 Subject: [PATCH 26/31] Update lib/src/rules/implicit_reopen.dart Co-authored-by: Erik Ernst --- lib/src/rules/implicit_reopen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 9c2ac732d..e316da397 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -16,7 +16,7 @@ const _details = r''' Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers, authors can control whether classes and mixins allow being implemented, extended, and/or mixed in from outside of the library where they're defined. -In some cases, it's possible for an author to inadvertantly relax these controls +In some cases, it's possible for an author to inadvertently relax these controls and implicitly "reopen" a class. This lint guards against unintentionally reopening a type by requiring such From d789444fbed38ede34761af56d9921bbab73d860 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 22 Mar 2023 13:16:23 -0700 Subject: [PATCH 27/31] Update lib/src/rules/implicit_reopen.dart Co-authored-by: Erik Ernst --- lib/src/rules/implicit_reopen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index e316da397..4689e4c0a 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -17,7 +17,7 @@ Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers, authors can control whether classes and mixins allow being implemented, extended, and/or mixed in from outside of the library where they're defined. In some cases, it's possible for an author to inadvertently relax these controls -and implicitly "reopen" a class. +and implicitly "reopen" a class. (A similar reopening cannot occur with a mixin.) This lint guards against unintentionally reopening a type by requiring such cases to be made explicit with the From c6db86f3c437a3818a87dba0db1e35d428d22bbc Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 22 Mar 2023 13:16:33 -0700 Subject: [PATCH 28/31] Update lib/src/rules/implicit_reopen.dart Co-authored-by: Erik Ernst --- lib/src/rules/implicit_reopen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 4689e4c0a..12695fe47 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -19,7 +19,7 @@ extended, and/or mixed in from outside of the library where they're defined. In some cases, it's possible for an author to inadvertently relax these controls and implicitly "reopen" a class. (A similar reopening cannot occur with a mixin.) -This lint guards against unintentionally reopening a type by requiring such +This lint guards against unintentionally reopening a class by requiring such cases to be made explicit with the [`@reopen`](https://pub.dev/documentation/meta/latest/meta/reopen-constant.html) annotation in `package:meta`. From a564b306152227d8200eda67a9cf541a4d3423da Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 22 Mar 2023 13:16:48 -0700 Subject: [PATCH 29/31] Update lib/src/rules/implicit_reopen.dart Co-authored-by: Erik Ernst --- lib/src/rules/implicit_reopen.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 12695fe47..8f5ffdd4b 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -13,7 +13,8 @@ const _desc = r"Don't implicitly reopen classes or mixins."; /// todo(pq): link out to (upcoming) dart.dev docs. /// https://github.com/dart-lang/site-www/issues/4497 const _details = r''' -Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers, +Using an `interface`, `base`, `final`, or `sealed` modifier on a class, +or a `base` modifier on a mixin, authors can control whether classes and mixins allow being implemented, extended, and/or mixed in from outside of the library where they're defined. In some cases, it's possible for an author to inadvertently relax these controls From 5832e5bdc849966f551d86a2d4c5c77af4c636b5 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 22 Mar 2023 13:19:38 -0700 Subject: [PATCH 30/31] Update lib/src/rules/implicit_reopen.dart Co-authored-by: Erik Ernst --- lib/src/rules/implicit_reopen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 8f5ffdd4b..22bc9fb0c 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -29,7 +29,7 @@ annotation in `package:meta`. ```dart interface class I {} -class C extends I {} +class C extends I {} // LINT ``` **GOOD:** From bdb1539af42bb29651ad01f6efcde15c653a769b Mon Sep 17 00:00:00 2001 From: pq Date: Wed, 22 Mar 2023 13:22:46 -0700 Subject: [PATCH 31/31] fdbk --- lib/src/rules/implicit_reopen.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/rules/implicit_reopen.dart b/lib/src/rules/implicit_reopen.dart index 22bc9fb0c..17243286e 100644 --- a/lib/src/rules/implicit_reopen.dart +++ b/lib/src/rules/implicit_reopen.dart @@ -8,7 +8,7 @@ import 'package:analyzer/dart/element/element.dart'; import '../analyzer.dart'; -const _desc = r"Don't implicitly reopen classes or mixins."; +const _desc = r"Don't implicitly reopen classes."; /// todo(pq): link out to (upcoming) dart.dev docs. /// https://github.com/dart-lang/site-www/issues/4497 @@ -39,6 +39,12 @@ interface class I {} final class C extends I {} ``` +```dart +interface class I {} + +final class C extends I {} +``` + ```dart import 'package:meta/meta.dart';