blob: 0111c4adfa689746c67a853dc2f5beb097dfa550 [file] [log] [blame] [edit]
// Copyright (c) 2015, 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/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_system.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/lint/linter_visitor.dart' show RuleVisitorRegistry;
import 'package:analyzer/src/lint/pub.dart';
import 'package:analyzer/workspace/workspace.dart';
export 'package:analyzer/analysis_rule/rule_state.dart'
show dart2_12, dart3, dart3_3, RuleState;
export 'package:analyzer/src/lint/linter_visitor.dart' show NodeLintRegistry;
/// Returns whether [filePath] is in the top-level `lib` directory in [package].
bool _isInLibDir(String? filePath, WorkspacePackage? package) {
if (package == null) return false;
if (filePath == null) return false;
var libDir = package.root.getChildAssumingFolder('lib');
return libDir.contains(filePath);
}
/// A soon-to-be deprecated alias for [RuleContext].
typedef LinterContext = RuleContext;
/// Describes an [AbstractAnalysisRule] which reports diagnostics using exactly
/// one [DiagnosticCode].
typedef LintRule = AnalysisRule;
/// 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).
sealed class AbstractAnalysisRule {
/// Used to report lints and warnings.
/// NOTE: this is set by the framework before any node processors start
/// visiting nodes.
late ErrorReporter _reporter;
/// Short description suitable for display in console output.
final String description;
/// The rule name.
final String name;
/// The state of this analysis rule, optionally indicating the "version" that
/// this state started applying to this 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 ids.
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 [ErrorReporter] for the [CompilationUnit] currently being
/// visited.
set reporter(ErrorReporter 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.
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {}
void _reportAtNode(
AstNode? node, {
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
required DiagnosticCode diagnosticCode,
}) {
if (node != null && !node.isSynthetic) {
_reporter.atNode(
node,
diagnosticCode,
arguments: arguments,
contextMessages: contextMessages,
);
}
}
void _reportAtOffset(
int offset,
int length, {
required DiagnosticCode diagnosticCode,
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
}) {
_reporter.atOffset(
offset: offset,
length: length,
diagnosticCode: diagnosticCode,
arguments: arguments,
contextMessages: contextMessages,
);
}
void _reportAtPubNode(
PSNode 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,
errorCode: diagnosticCode,
arguments: arguments,
contextMessages: contextMessages,
);
_reporter.reportError(diagnostic);
}
void _reportAtToken(
Token token, {
required DiagnosticCode diagnosticCode,
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
}) {
if (!token.isSynthetic) {
_reporter.atToken(
token,
diagnosticCode,
arguments: arguments,
contextMessages: contextMessages,
);
}
}
}
/// Describes an [AbstractAnalysisRule] which reports exactly one type of
/// diagnostic (one [DiagnosticCode]).
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].
void 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].
void 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].
void reportAtPubNode(
PSNode node, {
List<Object> arguments = const [],
List<DiagnosticMessage> contextMessages = const [],
}) => _reportAtPubNode(
node,
diagnosticCode: diagnosticCode,
arguments: arguments,
contextMessages: contextMessages,
);
/// Reports a diagnostic at [token], with message [arguments] and
/// [contextMessages].
void 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).
abstract class MultiAnalysisRule extends AbstractAnalysisRule {
MultiAnalysisRule({
required super.name,
required super.description,
super.state,
});
/// Reports [diagnosticCode] at [node] with message [arguments] and
/// [contextMessages].
void 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].
void 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].
void reportAtPubNode(
PSNode node, {
required DiagnosticCode diagnosticCode,
List<Object> arguments = const [],
List<DiagnosticMessage> contextMessages = const [],
}) {
// Cache error and location info for creating `AnalysisErrorInfo`s.
var error = Diagnostic.tmp(
source: node.source,
offset: node.span.start.offset,
length: node.span.length,
errorCode: diagnosticCode,
arguments: arguments,
contextMessages: contextMessages,
);
_reporter.reportError(error);
}
/// Reports [diagnosticCode] at [token], with message [arguments] and
/// [contextMessages].
void reportAtToken(
Token token, {
required DiagnosticCode diagnosticCode,
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
}) => _reportAtToken(
token,
diagnosticCode: diagnosticCode,
arguments: arguments,
contextMessages: contextMessages,
);
}
/// A [RuleContext] for a library, parsed into [ParsedUnitResult]s.
///
/// This is available for analysis rules that can operate on parsed,
/// unresolved syntax trees.
final class RuleContextWithParsedResults implements RuleContext {
@override
final List<RuleContextUnit> allUnits;
@override
final RuleContextUnit definingUnit;
@override
RuleContextUnit? currentUnit;
RuleContextWithParsedResults(this.allUnits, this.definingUnit);
@override
bool get isInLibDir =>
_isInLibDir(definingUnit.unit.declaredFragment?.source.fullName, package);
@override
bool get isInTestDirectory => false;
@override
LibraryElement get libraryElement =>
throw UnsupportedError(
'RuleContext with parsed results does not include a LibraryElement',
);
@override
WorkspacePackage? get package => null;
@override
TypeProvider get typeProvider =>
throw UnsupportedError(
'RuleContext with parsed results does not include a TypeProvider',
);
@override
TypeSystem get typeSystem =>
throw UnsupportedError(
'RuleContext with parsed results does not include a TypeSystem',
);
@override
bool isFeatureEnabled(Feature feature) =>
throw UnsupportedError(
'RuleContext with parsed results does not include a LibraryElement',
);
}
/// A [RuleContext] for a library, resolved into [ResolvedUnitResult]s.
final class RuleContextWithResolvedResults implements RuleContext {
@override
final List<RuleContextUnit> allUnits;
@override
final RuleContextUnit definingUnit;
@override
RuleContextUnit? currentUnit;
@override
final WorkspacePackage? package;
@override
final TypeProvider typeProvider;
@override
final TypeSystem typeSystem;
RuleContextWithResolvedResults(
this.allUnits,
this.definingUnit,
this.typeProvider,
this.typeSystem,
this.package,
);
@override
bool get isInLibDir =>
_isInLibDir(definingUnit.unit.declaredFragment?.source.fullName, package);
@override
bool get isInTestDirectory {
if (package case var package?) {
var file = definingUnit.file;
return package.isInTestDirectory(file);
}
return false;
}
@override
LibraryElement get libraryElement =>
definingUnit.unit.declaredFragment!.element;
@override
bool isFeatureEnabled(Feature feature) =>
libraryElement.featureSet.isEnabled(feature);
}