blob: 6e5e15438570802c8f97e131138506a3e144a1fb [file] [log] [blame]
// 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/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_state.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import '../analyzer.dart';
const _desc = r"Don't implicitly reopen classes.";
class ImplicitReopen extends LintRule {
ImplicitReopen()
: super(
name: LintNames.implicit_reopen,
description: _desc,
state: const RuleState.experimental(),
);
@override
DiagnosticCode get diagnosticCode => LinterLintCode.implicitReopen;
@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
var visitor = _Visitor(this);
registry.addClassDeclaration(this, visitor);
registry.addClassTypeAlias(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;
_Visitor(this.rule);
void checkElement(
InterfaceElement? element,
NamedCompilationUnitMember node, {
required String type,
}) {
if (element is! ClassElement) return;
if (element.metadata.hasReopen) return;
if (element.isSealed) return;
if (element.isMixinClass) return;
var library = element.library;
var supertype = element.supertype?.element;
if (supertype is! ClassElement) return;
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,
}) {
var targetName = target.name;
var otherName = other.name;
if (targetName != null && otherName != null) {
rule.reportAtToken(
member.name,
arguments: [type, targetName, otherName, reason],
);
}
}
@override
void visitClassDeclaration(ClassDeclaration node) {
checkElement(node.declaredFragment?.element, node, type: 'class');
}
@override
visitClassTypeAlias(ClassTypeAlias node) {
checkElement(node.declaredFragment?.element, node, type: 'class');
}
}
extension on ClassElement {
bool get hasNoModifiers => !isInterface && !isBase && !isSealed && !isFinal;
}