blob: d245ee9ee6441e1c65ace528f483d1b2ab282c6b [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);
}
}
}
_reportUnknownAndDuplicateIgnores(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);
}
}
}
_reportUnknownAndDuplicateIgnores(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.
//
_reportUnnecessaryIgnores(ignoredForFile);
for (var ignoredOnLine in ignoredOnLineMap.values) {
_reportUnnecessaryIgnores(ignoredOnLine);
}
}
/// Report the names that are [unignorable] or [duplicated] and remove them
/// from the [list] of names from which they were extracted.
void _reportUnknownAndDuplicateIgnores(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(HintCode.DUPLICATE_IGNORE,
ignoredElement.offset, name.length, [name]);
list.remove(ignoredElement);
} else if (ignoredElement is DiagnosticType) {
_errorReporter.reportErrorForOffset(
HintCode.DUPLICATE_IGNORE,
ignoredElement.offset,
ignoredElement.length,
[ignoredElement.type],
);
list.remove(ignoredElement);
}
}
}
/// Report the [ignoredNames] as being unnecessary.
void _reportUnnecessaryIgnores(List<IgnoredElement> ignoredNames) {
// TODO(brianwilkerson) Uncomment the code below after the unnecessary
// ignores in the Flutter code base have been cleaned up.
// for (var ignoredName in ignoredNames) {
// var name = ignoredName.name;
// _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);
}
}