blob: 2596ac9039c7b527e573872d5bd59699d1c9838d [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 'dart:collection';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/generated/source.dart';
/// Information about analysis `//ignore:` and `//ignore_for_file` comments
/// within a source file.
class IgnoreInfo {
/// Instance shared by all cases without matches.
static final IgnoreInfo _EMPTY_INFO = new IgnoreInfo();
/// A regular expression for matching 'ignore' comments. Produces matches
/// containing 2 groups. For example:
///
/// * ['//ignore: error_code', 'error_code']
///
/// Resulting codes may be in a list ('error_code_1,error_code2').
static final RegExp _IGNORE_MATCHER =
new RegExp(r'//+[ ]*ignore:(.*)$', multiLine: true);
/// A regular expression for matching 'ignore_for_file' comments. Produces
/// matches containing 2 groups. For example:
///
/// * ['//ignore_for_file: error_code', 'error_code']
///
/// Resulting codes may be in a list ('error_code_1,error_code2').
static final RegExp _IGNORE_FOR_FILE_MATCHER =
new RegExp(r'//[ ]*ignore_for_file:(.*)$', multiLine: true);
final Map<int, List<String>> _ignoreMap = new HashMap<int, List<String>>();
final Set<String> _ignoreForFileSet = new HashSet<String>();
/// Whether this info object defines any ignores.
bool get hasIgnores => ignores.isNotEmpty || _ignoreForFileSet.isNotEmpty;
/// Iterable of error codes ignored for the whole file.
Iterable<String> get ignoreForFiles => _ignoreForFileSet;
/// Map of line numbers to associated ignored error codes.
Map<int, Iterable<String>> get ignores => _ignoreMap;
/// Ignore this [errorCode] at [line].
void add(int line, String errorCode) {
_ignoreMap.putIfAbsent(line, () => new List<String>()).add(errorCode);
}
/// Ignore these [errorCodes] at [line].
void addAll(int line, Iterable<String> errorCodes) {
_ignoreMap.putIfAbsent(line, () => new List<String>()).addAll(errorCodes);
}
/// Ignore these [errorCodes] in the whole file.
void addAllForFile(Iterable<String> errorCodes) {
_ignoreForFileSet.addAll(errorCodes);
}
/// Test whether this [errorCode] is ignored at the given [line].
bool ignoredAt(String errorCode, int line) =>
_ignoreForFileSet.contains(errorCode) ||
_ignoreMap[line]?.contains(errorCode) == true;
/// Calculate ignores for the given [content] with line [info].
static IgnoreInfo calculateIgnores(String content, LineInfo info) {
Iterable<Match> matches = _IGNORE_MATCHER.allMatches(content);
Iterable<Match> fileMatches = _IGNORE_FOR_FILE_MATCHER.allMatches(content);
if (matches.isEmpty && fileMatches.isEmpty) {
return _EMPTY_INFO;
}
IgnoreInfo ignoreInfo = new IgnoreInfo();
for (Match match in matches) {
// See _IGNORE_MATCHER for format --- note the possibility of error lists.
Iterable<String> codes = match
.group(1)
.split(',')
.map((String code) => code.trim().toLowerCase());
CharacterLocation location = info.getLocation(match.start);
int lineNumber = location.lineNumber;
String beforeMatch = content.substring(
info.getOffsetOfLine(lineNumber - 1),
info.getOffsetOfLine(lineNumber - 1) + location.columnNumber - 1);
if (beforeMatch.trim().isEmpty) {
// The comment is on its own line, so it refers to the next line.
ignoreInfo.addAll(lineNumber + 1, codes);
} else {
// The comment sits next to code, so it refers to its own line.
ignoreInfo.addAll(lineNumber, codes);
}
}
for (Match match in fileMatches) {
Iterable<String> codes = match
.group(1)
.split(',')
.map((String code) => code.trim().toLowerCase());
ignoreInfo.addAllForFile(codes);
}
return ignoreInfo;
}
}