blob: 24860cd9e832efd0534a45eb71022c1ffdfd71d4 [file] [log] [blame]
// Copyright (c) 2020, 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/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/ignore_comments/ignore_info.dart';
/// Used to validate the ignore comments in a single file.
class IgnoreValidator {
/// The error reporter to which errors are to be reported.
final ErrorReporter _errorReporter;
/// The diagnostics that are reported in the file being analyzed.
final List<AnalysisError> _reportedErrors;
/// The information about the ignore comments in the file being analyzed.
final IgnoreInfo _ignoreInfo;
/// The line info for the file being analyzed.
final LineInfo _lineInfo;
/// A list of the names and unique names of all known error codes that can't
/// be ignored. Note that this list is incomplete. Plugins might well define
/// diagnostics with a severity of `ERROR`, but we won't be able to flag their
/// use because we have no visibility of them here.
final Set<String> _unignorableNames;
/// Initialize a newly created validator to report any issues with ignore
/// comments in the file being analyzed. The diagnostics will be reported to
/// the [_errorReporter].
IgnoreValidator(this._errorReporter, this._reportedErrors, this._ignoreInfo,
this._lineInfo, this._unignorableNames);
/// Report any issues with ignore comments in the file being analyzed.
void reportErrors() {
if (!_ignoreInfo.hasIgnores) {
return;
}
var ignoredOnLineMap = _ignoreInfo.ignoredOnLine;
var ignoredForFile = _ignoreInfo.ignoredForFile;
//
// Report and remove any un-ignorable or duplicated names.
//
var namesIgnoredForFile = <String>{};
var typesIgnoredForFile = <String>{};
var unignorable = <DiagnosticName>[];
var duplicated = <IgnoredElement>[];
for (var ignoredElement in ignoredForFile) {
if (ignoredElement is DiagnosticName) {
var name = ignoredElement.name;
if (_unignorableNames.contains(name)) {
unignorable.add(ignoredElement);
} else if (!namesIgnoredForFile.add(name)) {
duplicated.add(ignoredElement);
}
} else if (ignoredElement is DiagnosticType) {
if (!typesIgnoredForFile.add(ignoredElement.type)) {
duplicated.add(ignoredElement);
}
}
}
_reportUnignorableAndDuplicateIgnores(
unignorable, duplicated, ignoredForFile);
for (var ignoredOnLine in ignoredOnLineMap.values) {
var namedIgnoredOnLine = <String>{};
var typesIgnoredOnLine = <String>{};
var unignorable = <IgnoredElement>[];
var duplicated = <IgnoredElement>[];
for (var ignoredElement in ignoredOnLine) {
if (ignoredElement is DiagnosticName) {
var name = ignoredElement.name;
if (_unignorableNames.contains(name)) {
unignorable.add(ignoredElement);
} else if (namesIgnoredForFile.contains(name) ||
!namedIgnoredOnLine.add(name)) {
duplicated.add(ignoredElement);
}
} else if (ignoredElement is DiagnosticType) {
var type = ignoredElement.type;
if (typesIgnoredForFile.contains(type) ||
!typesIgnoredOnLine.add(type)) {
duplicated.add(ignoredElement);
}
}
}
_reportUnignorableAndDuplicateIgnores(
unignorable, duplicated, ignoredOnLine);
}
//
// Remove all of the errors that are actually being ignored.
//
for (var error in _reportedErrors) {
var lineNumber = _lineInfo.getLocation(error.offset).lineNumber;
var ignoredOnLine = ignoredOnLineMap[lineNumber];
ignoredForFile.removeByName(error.ignoreName);
ignoredForFile.removeByName(error.ignoreUniqueName);
ignoredOnLine?.removeByName(error.ignoreName);
ignoredOnLine?.removeByName(error.ignoreUniqueName);
}
//
// Report any remaining ignored names as being unnecessary.
//
_reportUnnecessaryOrRemovedOrDeprecatedIgnores(ignoredForFile);
for (var ignoredOnLine in ignoredOnLineMap.values) {
_reportUnnecessaryOrRemovedOrDeprecatedIgnores(ignoredOnLine);
}
}
/// Report the names that are [unignorable] or [duplicated] and remove them
/// from the [list] of names from which they were extracted.
void _reportUnignorableAndDuplicateIgnores(List<IgnoredElement> unignorable,
List<IgnoredElement> duplicated, List<IgnoredElement> list) {
// TODO(brianwilkerson) Uncomment the code below after the unignorable
// ignores in the Flutter code base have been cleaned up.
// for (var unignorableName in unignorable) {
// var name = unignorableName.name;
// _errorReporter.reportErrorForOffset(HintCode.UNIGNORABLE_IGNORE,
// unignorableName.offset, name.length, [name]);
// list.remove(unignorableName);
// }
for (var ignoredElement in duplicated) {
if (ignoredElement is DiagnosticName) {
var name = ignoredElement.name;
_errorReporter.reportErrorForOffset(WarningCode.DUPLICATE_IGNORE,
ignoredElement.offset, name.length, [name]);
list.remove(ignoredElement);
} else if (ignoredElement is DiagnosticType) {
_errorReporter.reportErrorForOffset(
WarningCode.DUPLICATE_IGNORE,
ignoredElement.offset,
ignoredElement.length,
[ignoredElement.type],
);
list.remove(ignoredElement);
}
}
}
/// Report the [ignoredNames] as being unnecessary.
void _reportUnnecessaryOrRemovedOrDeprecatedIgnores(
List<IgnoredElement> ignoredNames) {
// todo(pq): find the right way to roll-out enablement and uncomment
// https://github.com/dart-lang/sdk/issues/51214
// for (var ignoredName in ignoredNames) {
// if (ignoredName is DiagnosticName) {
// var name = ignoredName.name;
// var rule = Registry.ruleRegistry.getRule(name);
// if (rule != null) {
// var state = rule.state;
// var since = state.since.toString();
// if (state is DeprecatedState) {
// // todo(pq): implement
// } else if (state is RemovedState) {
// var replacedBy = state.replacedBy;
// if (replacedBy != null) {
// _errorReporter.reportErrorForOffset(HintCode.REPLACED_LINT_USE,
// ignoredName.offset, name.length, [name, since, replacedBy]);
// continue;
// } else {
// _errorReporter.reportErrorForOffset(HintCode.REMOVED_LINT_USE,
// ignoredName.offset, name.length, [name, since]);
// continue;
// }
// }
// }
//
// // TODO(brianwilkerson) Uncomment the code below after the unnecessary
// // ignores in the Flutter code base have been cleaned up.
// // _errorReporter.reportErrorForOffset(
// // HintCode.UNNECESSARY_IGNORE, ignoredName.offset, name.length,
// // [name]);
// }
// }
}
}
extension on AnalysisError {
String get ignoreName => errorCode.name.toLowerCase();
String get ignoreUniqueName {
String uniqueName = errorCode.uniqueName;
int period = uniqueName.indexOf('.');
if (period >= 0) {
uniqueName = uniqueName.substring(period + 1);
}
return uniqueName.toLowerCase();
}
}
extension on List<IgnoredElement> {
void removeByName(String name) {
removeWhere((ignoredElement) =>
ignoredElement is DiagnosticName && ignoredElement.name == name);
}
}