| // Copyright (c) 2026, 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:_fe_analyzer_shared/src/base/analyzer_public_api.dart'; | |
| import 'package:analyzer/analysis_rule/pubspec.dart'; | |
| 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/token.dart'; | |
| import 'package:analyzer/diagnostic/diagnostic.dart'; | |
| import 'package:analyzer/error/error.dart'; | |
| import 'package:analyzer/error/listener.dart'; | |
| import 'package:analyzer/source/source_range.dart'; | |
| import 'package:analyzer/src/dart/ast/ast.dart' show ToBeDeprecated; | |
| import 'package:analyzer/src/lint/pub.dart'; | |
| import 'package:meta/meta.dart'; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| /// Describes a static analysis rule, either a lint rule (which must be enabled | |
| /// via analysis options) or a warning rule (which is enabled by default). | |
| @AnalyzerPublicApi( | |
| message: 'Exported by package:analyzer/analysis_rule/analysis_rule.dart', | |
| ) | |
| sealed class AbstractAnalysisRule { | |
| /// Used to report lints and warnings. | |
| /// | |
| /// NOTE: this is set by the framework before any node processors start | |
| /// visiting nodes. | |
| late DiagnosticReporter _reporter; | |
| /// Short description suitable for display in console output and IDEs. | |
| /// | |
| /// This text may be used when providing information about a rule listed in | |
| /// analysis options file, or to suggest enabling a rule. | |
| final String description; | |
| /// The rule name. | |
| final String name; | |
| /// The state of this analysis rule. | |
| /// | |
| /// See [RuleState] for a list of the possible states and when they should be | |
| /// applied to a given rule. | |
| final RuleState state; | |
| AbstractAnalysisRule({ | |
| required this.name, | |
| required this.description, | |
| this.state = const RuleState.stable(), | |
| }); | |
| /// Indicates whether this analysis rule can work with just the parsed | |
| /// information or if it requires a resolved unit. | |
| bool get canUseParsedResult => false; | |
| /// The diagnostic codes associated with this analysis rule. | |
| List<DiagnosticCode> get diagnosticCodes; | |
| /// A list of incompatible rule names. | |
| /// | |
| /// Two rules are incompatible if there are situations where it is difficult | |
| /// or impossible to comply with both. The analyzer will warn if two | |
| /// incompatible rules are both enabled in analysis options. | |
| List<String> get incompatibleRules => const []; | |
| /// A visitor that visits a [Pubspec] to perform analysis. | |
| /// | |
| /// Diagnostics are reported via this [AbstractAnalysisRule]'s error | |
| /// [reporter]. | |
| PubspecVisitor? get pubspecVisitor => null; | |
| /// Sets the [DiagnosticReporter] for the [CompilationUnit] currently being | |
| /// visited. | |
| set reporter(DiagnosticReporter value) => _reporter = value; | |
| /// Registers node processors in the given [registry]. | |
| /// | |
| /// The node processors may use the provided [context] to access information | |
| /// that is not available from the AST nodes or their associated elements. | |
| @ToBeDeprecated('Use registerNodeProcessors2 instead') | |
| void registerNodeProcessors( | |
| RuleVisitorRegistry registry, | |
| RuleContext context, | |
| ) {} | |
| /// Registers node processors in the given [registry]. | |
| /// | |
| /// The node processors may use the provided [context] to access information | |
| /// that is not available from the AST nodes or their associated elements. | |
| @experimental | |
| void registerNodeProcessors2( | |
| RuleVisitorRegistry2 registry, | |
| RuleContext context, | |
| ) {} | |
| Diagnostic? _reportAtNode( | |
| AstNode? node, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| required DiagnosticCode diagnosticCode, | |
| }) { | |
| if (node != null && !node.isSynthetic) { | |
| return _reporter.atNode( | |
| node, | |
| diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| } | |
| return null; | |
| } | |
| Diagnostic _reportAtOffset( | |
| int offset, | |
| int length, { | |
| required DiagnosticCode diagnosticCode, | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) { | |
| return _reporter.atOffset( | |
| offset: offset, | |
| length: length, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| } | |
| Diagnostic _reportAtPubNode( | |
| PubspecNodeImpl node, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage> contextMessages = const [], | |
| required DiagnosticCode diagnosticCode, | |
| }) { | |
| // Cache diagnostic and location info for creating `AnalysisErrorInfo`s. | |
| var diagnostic = Diagnostic.tmp( | |
| source: node.source, | |
| offset: node.span.start.offset, | |
| length: node.span.length, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| _reporter.reportError(diagnostic); | |
| return diagnostic; | |
| } | |
| Diagnostic _reportAtSourceRange( | |
| SourceRange sourceRange, { | |
| required DiagnosticCode diagnosticCode, | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) { | |
| return _reporter.atSourceRange( | |
| sourceRange, | |
| diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| } | |
| Diagnostic? _reportAtToken( | |
| Token token, { | |
| required DiagnosticCode diagnosticCode, | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) { | |
| if (!token.isSynthetic) { | |
| return _reporter.atToken( | |
| token, | |
| diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| } | |
| return null; | |
| } | |
| } | |
| /// Describes an [AbstractAnalysisRule] which reports exactly one type of | |
| /// diagnostic (one [DiagnosticCode]). | |
| @AnalyzerPublicApi( | |
| message: 'Exported by package:analyzer/analysis_rule/analysis_rule.dart', | |
| ) | |
| abstract class AnalysisRule extends AbstractAnalysisRule { | |
| AnalysisRule({required super.name, required super.description, super.state}); | |
| /// The code to report for a violation. | |
| DiagnosticCode get diagnosticCode; | |
| @override | |
| List<DiagnosticCode> get diagnosticCodes => [diagnosticCode]; | |
| /// Reports a diagnostic at [node] with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@template analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| /// The [arguments] are interpolated into the [DiagnosticCode.problemMessage] | |
| /// and [DiagnosticCode.correctionMessage] text. If present, the first | |
| /// argument (at position 0) replaces each instance of `{0}`, the second | |
| /// argument (at position 1) replaces each instance of `{1}`, etc. | |
| /// {@endtemplate} | |
| Diagnostic? reportAtNode( | |
| AstNode? node, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) => _reportAtNode( | |
| node, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| /// Reports a diagnostic at [offset], with [length], with message [arguments] | |
| /// and [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic reportAtOffset( | |
| int offset, | |
| int length, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) => _reportAtOffset( | |
| offset, | |
| length, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| /// Reports a diagnostic at Pubspec [node], with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic reportAtPubNode( | |
| PubspecNode node, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage> contextMessages = const [], | |
| }) => _reportAtPubNode( | |
| node as PubspecNodeImpl, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| /// Reports a diagnostic at [sourceRange], with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic reportAtSourceRange( | |
| SourceRange sourceRange, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) => _reportAtSourceRange( | |
| sourceRange, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| /// Reports a diagnostic at [token], with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic? reportAtToken( | |
| Token token, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) => _reportAtToken( | |
| token, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| } | |
| /// Describes an [AbstractAnalysisRule] which reports diagnostics using multiple | |
| /// [DiagnosticCode]s). | |
| @AnalyzerPublicApi( | |
| message: 'Exported by package:analyzer/analysis_rule/analysis_rule.dart', | |
| ) | |
| abstract class MultiAnalysisRule extends AbstractAnalysisRule { | |
| MultiAnalysisRule({ | |
| required super.name, | |
| required super.description, | |
| super.state, | |
| }); | |
| /// Reports [diagnosticCode] at [node] with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic? reportAtNode( | |
| AstNode? node, { | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| required DiagnosticCode diagnosticCode, | |
| }) => _reportAtNode( | |
| node, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| /// Reports [diagnosticCode] at [offset], with [length], with message [arguments] | |
| /// and [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic reportAtOffset( | |
| int offset, | |
| int length, { | |
| required DiagnosticCode diagnosticCode, | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) => _reportAtOffset( | |
| offset, | |
| length, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| /// Reports [diagnosticCode] at Pubspec [node], with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic reportAtPubNode( | |
| PubspecNode node, { | |
| required DiagnosticCode diagnosticCode, | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage> contextMessages = const [], | |
| }) { | |
| // Cache diagnostic and location info for creating `AnalysisErrorInfo`s. | |
| var diagnostic = Diagnostic.tmp( | |
| source: (node as PubspecNodeImpl).source, | |
| offset: node.span.start.offset, | |
| length: node.span.length, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| _reporter.reportError(diagnostic); | |
| return diagnostic; | |
| } | |
| /// Reports [diagnosticCode] at [sourceRange], with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic reportAtSourceRange( | |
| SourceRange sourceRange, { | |
| required DiagnosticCode diagnosticCode, | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) => _reportAtSourceRange( | |
| sourceRange, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| /// Reports [diagnosticCode] at [token], with message [arguments] and | |
| /// [contextMessages]. | |
| /// | |
| /// {@macro analyzer.lib.analysis_rule.analysis_rule.arguments} | |
| Diagnostic? reportAtToken( | |
| Token token, { | |
| required DiagnosticCode diagnosticCode, | |
| List<Object> arguments = const [], | |
| List<DiagnosticMessage>? contextMessages, | |
| }) => _reportAtToken( | |
| token, | |
| diagnosticCode: diagnosticCode, | |
| arguments: arguments, | |
| contextMessages: contextMessages, | |
| ); | |
| } | |
| /// Describes an [AbstractAnalysisRule] whose implementation has been removed. | |
| @AnalyzerPublicApi( | |
| message: 'Exported by package:analyzer/analysis_rule/analysis_rule.dart', | |
| ) | |
| final class RemovedAnalysisRule extends MultiAnalysisRule { | |
| RemovedAnalysisRule({ | |
| required super.name, | |
| required super.description, | |
| Version? since, | |
| String? replacedBy, | |
| }) : super( | |
| // Note: the reason `RuleState.removed` is deprecated is to encourage | |
| // clients to use `AbstractAnalysisRule`, so this reference is ok. | |
| // ignore: deprecated_member_use_from_same_package | |
| state: RuleState.removed(since: since, replacedBy: replacedBy), | |
| ); | |
| @override | |
| List<DiagnosticCode> get diagnosticCodes => const []; | |
| } |