blob: 9f816753de6aa6787e513eb60a4ddb48e4736033 [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/dart/error/hint_codes.dart';
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/source.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) {
var filePath = _errorReporter.source.fullName;
for (var code in errorCodeValues) {
if (!isIgnorable(filePath, code)) {
unignorableNames.add(code.name.toLowerCase());
unignorableNames.add(code.uniqueName.toLowerCase());
}
}
}
/// 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 unignorable = <DiagnosticName>[];
var duplicated = <DiagnosticName>[];
for (var ignoredName in ignoredForFile) {
var name = ignoredName.name;
if (unignorableNames.contains(name)) {
unignorable.add(ignoredName);
} else if (!namesIgnoredForFile.add(name)) {
duplicated.add(ignoredName);
}
}
_reportUnknownAndDuplicateIgnores(unignorable, duplicated, ignoredForFile);
for (var ignoredOnLine in ignoredOnLineMap.values) {
var namedIgnoredOnLine = <String>{};
var unignorable = <DiagnosticName>[];
var duplicated = <DiagnosticName>[];
for (var ignoredName in ignoredOnLine) {
var name = ignoredName.name;
if (unignorableNames.contains(name)) {
unignorable.add(ignoredName);
} else if (namesIgnoredForFile.contains(name) ||
!namedIgnoredOnLine.add(name)) {
duplicated.add(ignoredName);
}
}
_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<DiagnosticName> unignorable,
List<DiagnosticName> duplicated, List<DiagnosticName> 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 ignoredName in duplicated) {
var name = ignoredName.name;
_errorReporter.reportErrorForOffset(
HintCode.DUPLICATE_IGNORE, ignoredName.offset, name.length, [name]);
list.remove(ignoredName);
}
}
/// Report the [ignoredNames] as being unnecessary.
void _reportUnnecessaryIgnores(List<DiagnosticName> 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]);
// }
}
static bool isIgnorable(String filePath, ErrorCode code) {
if (code.isIgnorable) {
return true;
}
// The [code] is not ignorable, but we've allowed a few "privileged"
// cases. Each is annotated with an issue which represents technical
// debt. Once cleaned up, we may remove this notion of "privileged".
// In the case of [CompileTimeErrorCode.IMPORT_INTERNAL_LIBRARY], we may
// just decide that it happens enough in tests that it can be declared
// an ignorable error, and in practice other back ends will prevent
// non-internal code from importing internal code.
if (code == CompileTimeErrorCode.UNDEFINED_FUNCTION ||
code == CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME) {
// Special case a small number of errors in Flutter code which are
// ignored. The erroneous code is found in a conditionally imported
// library, which uses a special version of the "dart:ui" library
// which the Analyzer does not use during analysis. See
// https://github.com/flutter/flutter/issues/52899.
if (filePath.contains('flutter')) {
return true;
}
}
if ((code == CompileTimeErrorCode.IMPORT_INTERNAL_LIBRARY ||
code == CompileTimeErrorCode.UNDEFINED_ANNOTATION ||
code == ParserErrorCode.NATIVE_FUNCTION_BODY_IN_NON_SDK_CODE) &&
(filePath.contains('tests/compiler/dart2js') ||
filePath.contains('pkg/compiler/test'))) {
// Special case the dart2js language tests. Some of these import
// various internal libraries.
return true;
}
return false;
}
}
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<DiagnosticName> {
void removeByName(String name) {
removeWhere((ignoredName) => ignoredName.name == name);
}
}