Skip to content

Commit 58a6e3c

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Rewrite handling of ignore comments to use token structure rather than regex
This also captures the information needed for the hints we want to add around duplicated, erroneous and unnecessary ignore comments. Change-Id: Ibafa2a92a02cf8113c222680f4868c38166d94e8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155847 Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 4a268b7 commit 58a6e3c

File tree

3 files changed

+133
-20
lines changed

3 files changed

+133
-20
lines changed

pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ class LibraryAnalyzer {
559559

560560
LineInfo lineInfo = unit.lineInfo;
561561
_fileToLineInfo[file] = lineInfo;
562-
_fileToIgnoreInfo[file] = IgnoreInfo.calculateIgnores(content, lineInfo);
562+
_fileToIgnoreInfo[file] = IgnoreInfo.forDart(unit, content);
563563

564564
return unit;
565565
}

pkg/analyzer/lib/src/dart/micro/library_analyzer.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ class LibraryAnalyzer {
531531

532532
LineInfo lineInfo = unit.lineInfo;
533533
_fileToLineInfo[file] = lineInfo;
534-
_fileToIgnoreInfo[file] = IgnoreInfo.calculateIgnores(content, lineInfo);
534+
_fileToIgnoreInfo[file] = IgnoreInfo.forDart(unit, content);
535535

536536
return unit;
537537
}

pkg/analyzer/lib/src/ignore_comments/ignore_info.dart

Lines changed: 131 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:collection';
6-
5+
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/ast/token.dart';
77
import 'package:analyzer/source/line_info.dart';
8+
import 'package:analyzer/src/dart/ast/token.dart';
89
import 'package:analyzer/src/generated/source.dart';
910

1011
/// Information about analysis `//ignore:` and `//ignore_for_file` comments
@@ -31,26 +32,73 @@ class IgnoreInfo {
3132
static final RegExp _IGNORE_FOR_FILE_MATCHER =
3233
RegExp(r'//[ ]*ignore_for_file:(.*)$', multiLine: true);
3334

34-
final Map<int, List<String>> _ignoreMap = HashMap<int, List<String>>();
35+
/// A table mapping line numbers to the diagnostics that are ignored on that
36+
/// line.
37+
final Map<int, List<_DiagnosticName>> _ignoredOnLine = {};
3538

36-
final Set<String> _ignoreForFileSet = HashSet<String>();
39+
/// A list containing all of the diagnostics that are ignored for the whole
40+
/// file.
41+
final List<_DiagnosticName> _ignoredForFile = [];
3742

38-
/// Whether this info object defines any ignores.
39-
bool get hasIgnores => _ignoreMap.isNotEmpty || _ignoreForFileSet.isNotEmpty;
43+
IgnoreInfo();
4044

41-
/// Test whether this [errorCode] is ignored at the given [line].
42-
bool ignoredAt(String errorCode, int line) =>
43-
_ignoreForFileSet.contains(errorCode) ||
44-
_ignoreMap[line]?.contains(errorCode) == true;
45+
/// Initialize a newly created instance of this class to represent the ignore
46+
/// comments in the given compilation [unit].
47+
IgnoreInfo.forDart(CompilationUnit unit, String content) {
48+
var lineInfo = unit.lineInfo;
49+
for (var comment in unit.ignoreComments) {
50+
var lexeme = comment.lexeme;
51+
if (lexeme.contains('ignore:')) {
52+
var location = lineInfo.getLocation(comment.offset);
53+
var lineNumber = location.lineNumber;
54+
String beforeMatch = content.substring(
55+
lineInfo.getOffsetOfLine(lineNumber - 1),
56+
lineInfo.getOffsetOfLine(lineNumber - 1) +
57+
location.columnNumber -
58+
1);
59+
if (beforeMatch.trim().isEmpty) {
60+
// The comment is on its own line, so it refers to the next line.
61+
lineNumber++;
62+
}
63+
_ignoredOnLine
64+
.putIfAbsent(lineNumber, () => [])
65+
.addAll(comment.diagnosticNames);
66+
} else if (lexeme.contains('ignore_for_file:')) {
67+
_ignoredForFile.addAll(comment.diagnosticNames);
68+
}
69+
}
70+
}
71+
72+
/// Return `true` if there are any ignore comments in the file.
73+
bool get hasIgnores =>
74+
_ignoredOnLine.isNotEmpty || _ignoredForFile.isNotEmpty;
75+
76+
/// Return `true` if the [errorCode] is ignored at the given [line].
77+
bool ignoredAt(String errorCode, int line) {
78+
for (var name in _ignoredForFile) {
79+
if (name.matches(errorCode)) {
80+
return true;
81+
}
82+
}
83+
var ignoredOnLine = _ignoredOnLine[line];
84+
if (ignoredOnLine != null) {
85+
for (var name in ignoredOnLine) {
86+
if (name.matches(errorCode)) {
87+
return true;
88+
}
89+
}
90+
}
91+
return false;
92+
}
4593

4694
/// Ignore these [errorCodes] at [line].
47-
void _addAll(int line, Iterable<String> errorCodes) {
48-
_ignoreMap.putIfAbsent(line, () => <String>[]).addAll(errorCodes);
95+
void _addAll(int line, Iterable<_DiagnosticName> errorCodes) {
96+
_ignoredOnLine.putIfAbsent(line, () => []).addAll(errorCodes);
4997
}
5098

5199
/// Ignore these [errorCodes] in the whole file.
52-
void _addAllForFile(Iterable<String> errorCodes) {
53-
_ignoreForFileSet.addAll(errorCodes);
100+
void _addAllForFile(Iterable<_DiagnosticName> errorCodes) {
101+
_ignoredForFile.addAll(errorCodes);
54102
}
55103

56104
/// Calculate ignores for the given [content] with line [info].
@@ -64,10 +112,13 @@ class IgnoreInfo {
64112
IgnoreInfo ignoreInfo = IgnoreInfo();
65113
for (Match match in matches) {
66114
// See _IGNORE_MATCHER for format --- note the possibility of error lists.
67-
Iterable<String> codes = match
115+
// Note that the offsets are not being computed here. This shouldn't
116+
// affect older clients of this class because none of the previous APIs
117+
// depended on having offsets.
118+
Iterable<_DiagnosticName> codes = match
68119
.group(1)
69120
.split(',')
70-
.map((String code) => code.trim().toLowerCase());
121+
.map((String code) => _DiagnosticName(code.trim().toLowerCase(), -1));
71122
CharacterLocation location = info.getLocation(match.start);
72123
int lineNumber = location.lineNumber;
73124
String beforeMatch = content.substring(
@@ -82,13 +133,75 @@ class IgnoreInfo {
82133
ignoreInfo._addAll(lineNumber, codes);
83134
}
84135
}
136+
// Note that the offsets are not being computed here. This shouldn't affect
137+
// older clients of this class because none of the previous APIs depended on
138+
// having offsets.
85139
for (Match match in fileMatches) {
86-
Iterable<String> codes = match
140+
Iterable<_DiagnosticName> codes = match
87141
.group(1)
88142
.split(',')
89-
.map((String code) => code.trim().toLowerCase());
143+
.map((String code) => _DiagnosticName(code.trim().toLowerCase(), -1));
90144
ignoreInfo._addAllForFile(codes);
91145
}
92146
return ignoreInfo;
93147
}
94148
}
149+
150+
/// The name and location of a diagnostic name in an ignore comment.
151+
class _DiagnosticName {
152+
/// The name of the diagnostic being ignored.
153+
final String name;
154+
155+
/// The offset of the diagnostic in the source file.
156+
final int offset;
157+
158+
/// Initialize a newly created diagnostic name to have the given [name] and
159+
/// [offset].
160+
_DiagnosticName(this.name, this.offset);
161+
162+
/// Return `true` if this diagnostic name matches the given error code.
163+
bool matches(String errorCode) => name == errorCode;
164+
}
165+
166+
extension on CompilationUnit {
167+
/// Return all of the ignore comments in this compilation unit.
168+
Iterable<CommentToken> get ignoreComments sync* {
169+
Iterable<CommentToken> processPrecedingComments(Token currentToken) sync* {
170+
var comment = currentToken.precedingComments;
171+
while (comment != null) {
172+
var lexeme = comment.lexeme;
173+
var match = IgnoreInfo._IGNORE_MATCHER.matchAsPrefix(lexeme);
174+
if (match != null) {
175+
yield comment;
176+
} else {
177+
match = IgnoreInfo._IGNORE_FOR_FILE_MATCHER.matchAsPrefix(lexeme);
178+
if (match != null) {
179+
yield comment;
180+
}
181+
}
182+
comment = comment.next;
183+
}
184+
}
185+
186+
var currentToken = beginToken;
187+
while (currentToken != currentToken.next) {
188+
yield* processPrecedingComments(currentToken);
189+
currentToken = currentToken.next;
190+
}
191+
yield* processPrecedingComments(currentToken);
192+
}
193+
}
194+
195+
extension on CommentToken {
196+
/// Return the diagnostic names contained in this comment, assuming that it is
197+
/// a correctly formatted ignore comment.
198+
Iterable<_DiagnosticName> get diagnosticNames sync* {
199+
int offset = lexeme.indexOf(':') + 1;
200+
var names = lexeme.substring(offset).split(',');
201+
offset += this.offset;
202+
for (var name in names) {
203+
yield _DiagnosticName(name.trim().toLowerCase(), offset);
204+
offset += name.length + 1;
205+
}
206+
}
207+
}

0 commit comments

Comments
 (0)