| // 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/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/src/dart/ast/token.dart'; |
| |
| /// The name and location of a diagnostic name in an ignore comment. |
| class DiagnosticName implements IgnoredElement { |
| /// The name of the diagnostic being ignored. |
| final String name; |
| |
| final int offset; |
| |
| /// Initialize a newly created diagnostic name to have the given [name] and |
| /// [offset]. |
| DiagnosticName(this.name, this.offset); |
| |
| /// Return `true` if this diagnostic name matches the given error code. |
| @override |
| bool matches(ErrorCode errorCode) { |
| if (name == errorCode.name.toLowerCase()) { |
| return true; |
| } |
| var uniqueName = errorCode.uniqueName; |
| var period = uniqueName.indexOf('.'); |
| if (period >= 0) { |
| uniqueName = uniqueName.substring(period + 1); |
| } |
| return name == uniqueName.toLowerCase(); |
| } |
| } |
| |
| class DiagnosticType implements IgnoredElement { |
| final String type; |
| |
| final int offset; |
| |
| final int length; |
| |
| DiagnosticType(String type, this.offset, this.length) |
| : type = type.toLowerCase(); |
| |
| @override |
| bool matches(ErrorCode errorCode) => |
| type == errorCode.type.name.toLowerCase(); |
| } |
| |
| abstract class IgnoredElement { |
| bool matches(ErrorCode errorCode); |
| } |
| |
| /// Information about analysis `//ignore:` and `//ignore_for_file:` comments |
| /// within a source file. |
| class IgnoreInfo { |
| /// A regular expression for matching 'ignore' comments. |
| /// |
| /// Resulting codes may be in a list ('error_code_1,error_code2'). |
| static final RegExp IGNORE_MATCHER = RegExp(r'//+[ ]*ignore:'); |
| |
| /// A regular expression for matching 'ignore_for_file' comments. |
| /// |
| /// Resulting codes may be in a list ('error_code_1,error_code2'). |
| static final RegExp IGNORE_FOR_FILE_MATCHER = |
| RegExp(r'//[ ]*ignore_for_file:'); |
| |
| /// A table mapping line numbers to the elements (diagnostics and diagnostic |
| /// types) that are ignored on that line. |
| final Map<int, List<IgnoredElement>> _ignoredOnLine = {}; |
| |
| /// A list containing all of the elements (diagnostics and diagnostic types) |
| /// that are ignored for the whole file. |
| final List<IgnoredElement> _ignoredForFile = []; |
| |
| /// Initialize a newly created instance of this class to represent the ignore |
| /// comments in the given compilation [unit]. |
| IgnoreInfo.forDart(CompilationUnit unit, String content) { |
| var lineInfo = unit.lineInfo!; |
| for (var comment in unit.ignoreComments) { |
| var lexeme = comment.lexeme; |
| if (lexeme.contains('ignore:')) { |
| var location = lineInfo.getLocation(comment.offset); |
| var lineNumber = location.lineNumber; |
| var offsetOfLine = lineInfo.getOffsetOfLine(lineNumber - 1); |
| var beforeMatch = content.substring( |
| offsetOfLine, offsetOfLine + location.columnNumber - 1); |
| if (beforeMatch.trim().isEmpty) { |
| // The comment is on its own line, so it refers to the next line. |
| lineNumber++; |
| } |
| _ignoredOnLine |
| .putIfAbsent(lineNumber, () => []) |
| .addAll(comment.ignoredElements); |
| } else if (lexeme.contains('ignore_for_file:')) { |
| _ignoredForFile.addAll(comment.ignoredElements); |
| } |
| } |
| } |
| |
| /// Return `true` if there are any ignore comments in the file. |
| bool get hasIgnores => |
| _ignoredOnLine.isNotEmpty || _ignoredForFile.isNotEmpty; |
| |
| /// Return a list containing all of the diagnostics that are ignored for the |
| /// whole file. |
| List<IgnoredElement> get ignoredForFile => _ignoredForFile.toList(); |
| |
| /// Return a table mapping line numbers to the diagnostics that are ignored on |
| /// that line. |
| Map<int, List<IgnoredElement>> get ignoredOnLine { |
| Map<int, List<IgnoredElement>> ignoredOnLine = {}; |
| for (var entry in _ignoredOnLine.entries) { |
| ignoredOnLine[entry.key] = entry.value.toList(); |
| } |
| return ignoredOnLine; |
| } |
| |
| /// Return `true` if the [errorCode] is ignored at the given [line]. |
| bool ignoredAt(ErrorCode errorCode, int line) { |
| var ignoredDiagnostics = _ignoredOnLine[line]; |
| if (ignoredForFile.isEmpty && ignoredDiagnostics == null) { |
| return false; |
| } |
| if (ignoredForFile.any((name) => name.matches(errorCode))) { |
| return true; |
| } |
| if (ignoredDiagnostics == null) { |
| return false; |
| } |
| return ignoredDiagnostics.any((name) => name.matches(errorCode)); |
| } |
| } |
| |
| extension on CompilationUnit { |
| /// Return all of the ignore comments in this compilation unit. |
| Iterable<CommentToken> get ignoreComments sync* { |
| Iterable<CommentToken> processPrecedingComments(Token currentToken) sync* { |
| var comment = currentToken.precedingComments; |
| while (comment != null) { |
| var lexeme = comment.lexeme; |
| if (lexeme.startsWith(IgnoreInfo.IGNORE_MATCHER)) { |
| yield comment; |
| } else if (lexeme.startsWith(IgnoreInfo.IGNORE_FOR_FILE_MATCHER)) { |
| yield comment; |
| } |
| comment = comment.next as CommentToken?; |
| } |
| } |
| |
| var currentToken = beginToken; |
| while (currentToken != currentToken.next) { |
| yield* processPrecedingComments(currentToken); |
| currentToken = currentToken.next!; |
| } |
| yield* processPrecedingComments(currentToken); |
| } |
| } |
| |
| extension on CommentToken { |
| /// The error codes currently do not contain dollar signs, so we can be a bit |
| /// more restrictive in this test. |
| static final _errorCodeNameRegExp = RegExp(r'^[a-zA-Z][_a-z0-9A-Z]*$'); |
| |
| static final _errorTypeRegExp = |
| RegExp(r'^type[ ]*=[ ]*lint', caseSensitive: false); |
| |
| /// Return the diagnostic names contained in this comment, assuming that it is |
| /// a correctly formatted ignore comment. |
| Iterable<IgnoredElement> get ignoredElements sync* { |
| int offset = lexeme.indexOf(':') + 1; |
| var names = lexeme.substring(offset).split(','); |
| offset += this.offset; |
| for (var name in names) { |
| var trimmedName = name.trim(); |
| if (trimmedName.isNotEmpty) { |
| if (trimmedName.contains(_errorCodeNameRegExp)) { |
| var innerOffset = name.indexOf(trimmedName); |
| yield DiagnosticName(trimmedName.toLowerCase(), offset + innerOffset); |
| } else { |
| var match = _errorTypeRegExp.matchAsPrefix(trimmedName); |
| if (match != null) { |
| var innerOffset = name.indexOf(trimmedName); |
| yield DiagnosticType('lint', offset + innerOffset, name.length); |
| } |
| } |
| } |
| offset += name.length + 1; |
| } |
| } |
| } |