Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

+ implicit_reopen #4101

Merged
merged 33 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
89172fc
+ implicit_reopen
pq Mar 1, 2023
fc053db
feedback
pq Mar 1, 2023
74ec22c
docs
pq Mar 1, 2023
9fa0a6f
class type aliases
pq Mar 1, 2023
bb03472
fmt
pq Mar 1, 2023
4e5f345
fix description
pq Mar 1, 2023
9c83e39
hoist
pq Mar 1, 2023
1a1c5e9
--
pq Mar 1, 2023
20c5cb9
Merge branch 'main' into implicit_reopen
pq Mar 20, 2023
67848e3
rebase
pq Mar 20, 2023
309bd6e
revisit
pq Mar 21, 2023
809ec9f
++
pq Mar 21, 2023
21926ca
++
pq Mar 21, 2023
cb6618a
--
pq Mar 21, 2023
87f6d09
++
pq Mar 21, 2023
a8938cb
+ test
pq Mar 21, 2023
148c6ea
//
pq Mar 21, 2023
6937848
Fix #4165 by visiting SwitchPatternCase (#4169)
srawlins Mar 21, 2023
895fdc6
Use deps.dev for OpenSSF scorecard results link (#4184)
parlough Mar 21, 2023
7e83788
prefer_final_parameters tests (#4182)
pq Mar 21, 2023
8fba507
switch to the latest published version of the setup-dart action (#4186)
devoncarew Mar 21, 2023
d390f9d
test cleanup (#4185)
pq Mar 21, 2023
1bb7017
Support analyzer 5.9.0 (#4188)
srawlins Mar 22, 2023
617d940
Merge branch 'main' into implicit_reopen
pq Mar 22, 2023
ed9bafa
- mixin
pq Mar 22, 2023
f00b3da
rename
pq Mar 22, 2023
f2b3818
rename
pq Mar 22, 2023
d0e228c
Update lib/src/rules/implicit_reopen.dart
pq Mar 22, 2023
d789444
Update lib/src/rules/implicit_reopen.dart
pq Mar 22, 2023
c6db86f
Update lib/src/rules/implicit_reopen.dart
pq Mar 22, 2023
a564b30
Update lib/src/rules/implicit_reopen.dart
pq Mar 22, 2023
5832e5b
Update lib/src/rules/implicit_reopen.dart
pq Mar 22, 2023
bdb1539
fdbk
pq Mar 22, 2023
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
1 change: 1 addition & 0 deletions example/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ linter:
- hash_and_equals
- implementation_imports
- implicit_call_tearoffs
- implicit_reopen
- invalid_case_patterns
- iterable_contains_unrelated_type
- join_return_with_assignment
Expand Down
2 changes: 2 additions & 0 deletions lib/src/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,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';
Expand Down Expand Up @@ -310,6 +311,7 @@ void registerLintRules({bool inTestMode = false}) {
..register(HashAndEquals())
..register(ImplementationImports())
..register(ImplicitCallTearoffs())
..register(ImplicitReopen())
..register(InvariantBooleans())
..register(InvalidCasePatterns())
..register(IterableContainsUnrelatedType())
Expand Down
171 changes: 171 additions & 0 deletions lib/src/rules/implicit_reopen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// 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 '../analyzer.dart';

const _desc = r"Don't implicitly reopen classes or mixins.";
pq marked this conversation as resolved.
Show resolved Hide resolved

/// todo(pq): link out to (upcoming) dart.dev docs.
pq marked this conversation as resolved.
Show resolved Hide resolved
/// https://github.com/dart-lang/site-www/issues/4497
const _details = r'''
Using the `interface`, `final`, `base`, `mixin`, and `sealed` class modifiers,
pq marked this conversation as resolved.
Show resolved Hide resolved
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
pq marked this conversation as resolved.
Show resolved Hide resolved
and implicitly "reopen" a class or mixin.
pq marked this conversation as resolved.
Show resolved Hide resolved

This lint guards against unintentionally reopening a type by requiring such
pq marked this conversation as resolved.
Show resolved Hide resolved
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 {}
pq marked this conversation as resolved.
Show resolved Hide resolved
```

**GOOD:**
```dart
interface class I {}

final class C extends I {}
pq marked this conversation as resolved.
Show resolved Hide resolved
```

```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 {0} '{1}' reopens '{2}' because it is not marked '{3}'",
correctionMessage:
"Try marking '{1}' '{3}' 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 checkElement(InterfaceElement? element, NamedCompilationUnitMember node,
{required String type}) {
if (element == null) return;
if (element.hasReopen) return;
if (element.isSealed) return;
if (element.isMixinClass) return;

var library = element.library;
var supertype = element.superElement;
if (supertype.library != library) return;

if (element.isBase) {
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;
}
} else if (element.hasNoModifiers) {
if (supertype.isInterface) {
reportLint(node,
target: element,
other: supertype!,
reason: 'interface',
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
void visitClassDeclaration(ClassDeclaration node) {
checkElement(node.declaredElement, node, type: 'class');
}
}

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;
return false;
}

bool get isInterface {
var self = this;
if (self is ClassElement) return self.isInterface;
return false;
}

bool get isSealed {
var self = this;
if (self is ClassElement) return self.isSealed;
return false;
}

bool get isMixinClass {
var self = this;
if (self is ClassElement) return self.isMixinClass;
return false;
}

LibraryElement? get library => this?.library;

InterfaceElement? get superElement => this?.supertype?.element;
}
2 changes: 2 additions & 0 deletions test/rules/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import 'exhaustive_cases_test.dart' as exhaustive_cases;
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 'join_return_with_assignment_test.dart' as join_return_with_assignment;
import 'library_annotations_test.dart' as library_annotations;
Expand Down Expand Up @@ -159,6 +160,7 @@ void main() {
file_names.main();
flutter_style_todos.main();
hash_and_equals.main();
implicit_reopen.main();
invalid_case_patterns.main();
join_return_with_assignment.main();
library_annotations.main();
Expand Down
Loading