blob: 7ffa5439649d8a20447c7dead523ff82ce0f602a [file] [log] [blame]
// 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/dart/analysis/results.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/file_system/file_system.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/lint/linter_visitor.dart' show NodeLintRegistry;
import 'package:analyzer/src/lint/pub.dart';
import 'package:analyzer/src/lint/state.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
export 'package:analyzer/src/lint/linter_visitor.dart' show NodeLintRegistry;
export 'package:analyzer/src/lint/state.dart'
show dart2_12, dart3, dart3_3, State;
/// 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).
typedef AnalysisRule = LintRule;
/// Provides access to information needed by lint rules that is not available
/// from AST nodes or the element model.
abstract class LinterContext {
/// The list of all compilation units that make up the library under analysis,
/// including the defining compilation unit, all parts, and all augmentations.
List<LintRuleUnitContext> get allUnits;
/// The compilation unit being linted.
///
/// `null` when a unit is not currently being linted (for example when node
/// processors are being registered).
LintRuleUnitContext? get currentUnit;
/// The defining compilation unit of the library under analysis.
LintRuleUnitContext get definingUnit;
InheritanceManager3 get inheritanceManager;
/// Whether the [definingUnit]'s location is in a package's top-level 'lib'
/// directory, including locations deeply nested, and locations in the
/// package-implementation directory, 'lib/src'.
bool get isInLibDir;
/// Whether the [definingUnit] is in a [package]'s "test" directory.
bool get isInTestDirectory;
/// The library element representing the library that contains the compilation
/// unit being linted.
@experimental
LibraryElement? get libraryElement2;
/// The package in which the library being analyzed lives, or `null` if it
/// does not live in a package.
WorkspacePackage? get package;
TypeProvider get typeProvider;
TypeSystem get typeSystem;
static bool _isInLibDir(String? path, WorkspacePackage? package) {
if (package == null) return false;
if (path == null) return false;
var libDir = p.join(package.root, 'lib');
return p.isWithin(libDir, path);
}
}
/// A [LinterContext] for a library, resolved into [ParsedUnitResult]s.
final class LinterContextWithParsedResults implements LinterContext {
@override
final List<LintRuleUnitContext> allUnits;
@override
final LintRuleUnitContext definingUnit;
@override
LintRuleUnitContext? currentUnit;
@override
final InheritanceManager3 inheritanceManager = InheritanceManager3();
LinterContextWithParsedResults(this.allUnits, this.definingUnit);
@override
bool get isInLibDir => LinterContext._isInLibDir(
definingUnit.unit.declaredFragment?.source.fullName,
package,
);
@override
bool get isInTestDirectory => false;
@experimental
@override
LibraryElement get libraryElement2 =>
throw UnsupportedError(
'LinterContext with parsed results does not include a LibraryElement',
);
@override
WorkspacePackage? get package => null;
@override
TypeProvider get typeProvider =>
throw UnsupportedError(
'LinterContext with parsed results does not include a TypeProvider',
);
@override
TypeSystem get typeSystem =>
throw UnsupportedError(
'LinterContext with parsed results does not include a TypeSystem',
);
}
/// A [LinterContext] for a library, resolved into [ResolvedUnitResult]s.
final class LinterContextWithResolvedResults implements LinterContext {
@override
final List<LintRuleUnitContext> allUnits;
@override
final LintRuleUnitContext definingUnit;
@override
LintRuleUnitContext? currentUnit;
@override
final WorkspacePackage? package;
@override
final TypeProvider typeProvider;
@override
final TypeSystem typeSystem;
@override
final InheritanceManager3 inheritanceManager;
LinterContextWithResolvedResults(
this.allUnits,
this.definingUnit,
this.typeProvider,
this.typeSystem,
this.inheritanceManager,
this.package,
);
@override
bool get isInLibDir => LinterContext._isInLibDir(
definingUnit.libraryFragment.source.fullName,
package,
);
@override
bool get isInTestDirectory {
if (package case var package?) {
var file = definingUnit.file;
return package.isInTestDirectory(file);
}
return false;
}
@experimental
@override
LibraryElement get libraryElement2 => definingUnit.libraryFragment.element;
}
/// Describes a lint rule.
abstract class LintRule {
/// Used to report lint 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;
/// Lint name.
final String name;
/// The state of a lint, and optionally since when the state began.
final State state;
LintRule({
required this.name,
required this.description,
this.state = const State.stable(),
});
/// Indicates whether the lint rule can work with just the parsed information
/// or if it requires a resolved unit.
bool get canUseParsedResult => false;
/// A list of incompatible rule ids.
List<String> get incompatibleRules => const [];
/// The lint code associated with this linter, if it is only associated with a
/// single lint code.
///
/// Note that this property is just a convenient shorthand for a rule to
/// associate a lint rule with a single lint code. Use [lintCodes] for the
/// full list of (possibly multiple) lint codes which a lint rule may be
/// associated with.
LintCode get lintCode =>
throw UnimplementedError(
"'lintCode' is not implemented for $runtimeType",
);
/// The lint codes associated with this lint rule.
List<LintCode> get lintCodes => [lintCode];
@protected
// Protected so that lint rule visitors do not access this directly.
// TODO(srawlins): With the new availability of an ErrorReporter on
// LinterContextUnit, we should probably remove this reporter. But whatever
// the new API would be is not yet decided. It might also change with the
// notion of post-processing lint rules that have access to all unit
// reporters at once.
ErrorReporter get reporter => _reporter;
set reporter(ErrorReporter value) => _reporter = value;
/// Returns a visitor to be passed to pubspecs to perform lint
/// analysis.
///
/// Lint errors are reported via this [LintRule]'s error [reporter].
PubspecVisitor? getPubspecVisitor() => null;
/// 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(
NodeLintRegistry registry,
LinterContext context,
) {}
/// Reports a diagnostic at [node] with message [arguments] and
/// [contextMessages].
///
/// The error reported is either [errorCode] if that is passed in, otherwise
/// [lintCode].
void reportAtNode(
AstNode? node, {
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
ErrorCode? errorCode,
}) {
if (node != null && !node.isSynthetic) {
reporter.atNode(
node,
errorCode ?? lintCode,
arguments: arguments,
contextMessages: contextMessages,
);
}
}
/// Reports a diagnostic at [offset], with [length], with message [arguments]
/// and [contextMessages].
///
/// The error reported is either [errorCode] if that is passed in, otherwise
/// [lintCode].
void reportAtOffset(
int offset,
int length, {
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
ErrorCode? errorCode,
}) {
reporter.atOffset(
offset: offset,
length: length,
errorCode: errorCode ?? lintCode,
arguments: arguments,
contextMessages: contextMessages,
);
}
/// Reports a diagnostic at Pubspec [node], with message [arguments] and
/// [contextMessages].
///
/// The error reported is either [errorCode] if that is passed in, otherwise
/// [lintCode].
void reportAtPubNode(
PSNode node, {
List<Object> arguments = const [],
List<DiagnosticMessage> contextMessages = const [],
ErrorCode? errorCode,
}) {
// Cache error and location info for creating `AnalysisErrorInfo`s.
var error = AnalysisError.tmp(
source: node.source,
offset: node.span.start.offset,
length: node.span.length,
errorCode: errorCode ?? lintCode,
arguments: arguments,
contextMessages: contextMessages,
);
reporter.reportError(error);
}
/// Reports a diagnostic at [token], with message [arguments] and
/// [contextMessages].
///
/// The error reported is either [errorCode] if that is passed in, otherwise
/// [lintCode].
void reportAtToken(
Token token, {
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
ErrorCode? errorCode,
}) {
if (!token.isSynthetic) {
reporter.atToken(
token,
errorCode ?? lintCode,
arguments: arguments,
contextMessages: contextMessages,
);
}
}
// TODO(srawlins): Deprecate this in favor of [reportNode].
void reportLint(
AstNode? node, {
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
ErrorCode? errorCode,
}) => reportAtNode(
node,
arguments: arguments,
contextMessages: contextMessages,
errorCode: errorCode,
);
// TODO(srawlins): Deprecate this in favor of [reportOffset].
void reportLintForOffset(
int offset,
int length, {
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
ErrorCode? errorCode,
}) => reportAtOffset(
offset,
length,
arguments: arguments,
contextMessages: contextMessages,
errorCode: errorCode,
);
@Deprecated('Use reportAtToken')
void reportLintForToken(
Token token, {
List<Object> arguments = const [],
List<DiagnosticMessage>? contextMessages,
ErrorCode? errorCode,
}) => reportAtToken(
token,
arguments: arguments,
contextMessages: contextMessages,
errorCode: errorCode,
);
}
/// Provides access to information needed by lint rules that is not available
/// from AST nodes or the element model.
class LintRuleUnitContext {
final File file;
final String content;
final ErrorReporter errorReporter;
final CompilationUnit unit;
LintRuleUnitContext({
required this.file,
required this.content,
required this.errorReporter,
required this.unit,
});
/// The library fragment representing the compilation unit.
@experimental
LibraryFragment get libraryFragment => unit.declaredFragment!;
}