2
2
// for details. All rights reserved. Use of this source code is governed by a
3
3
// BSD-style license that can be found in the LICENSE file.
4
4
5
- import 'dart:collection ' ;
6
-
5
+ import 'package:analyzer/dart/ast/ast.dart ' ;
6
+ import 'package:analyzer/dart/ast/token.dart' ;
7
7
import 'package:analyzer/source/line_info.dart' ;
8
+ import 'package:analyzer/src/dart/ast/token.dart' ;
8
9
import 'package:analyzer/src/generated/source.dart' ;
9
10
10
11
/// Information about analysis `//ignore:` and `//ignore_for_file` comments
@@ -31,26 +32,73 @@ class IgnoreInfo {
31
32
static final RegExp _IGNORE_FOR_FILE_MATCHER =
32
33
RegExp (r'//[ ]*ignore_for_file:(.*)$' , multiLine: true );
33
34
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 = {};
35
38
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 = [];
37
42
38
- /// Whether this info object defines any ignores.
39
- bool get hasIgnores => _ignoreMap.isNotEmpty || _ignoreForFileSet.isNotEmpty;
43
+ IgnoreInfo ();
40
44
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
+ }
45
93
46
94
/// 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);
49
97
}
50
98
51
99
/// 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);
54
102
}
55
103
56
104
/// Calculate ignores for the given [content] with line [info] .
@@ -64,10 +112,13 @@ class IgnoreInfo {
64
112
IgnoreInfo ignoreInfo = IgnoreInfo ();
65
113
for (Match match in matches) {
66
114
// 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
68
119
.group (1 )
69
120
.split (',' )
70
- .map ((String code) => code.trim ().toLowerCase ());
121
+ .map ((String code) => _DiagnosticName ( code.trim ().toLowerCase (), - 1 ));
71
122
CharacterLocation location = info.getLocation (match.start);
72
123
int lineNumber = location.lineNumber;
73
124
String beforeMatch = content.substring (
@@ -82,13 +133,75 @@ class IgnoreInfo {
82
133
ignoreInfo._addAll (lineNumber, codes);
83
134
}
84
135
}
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.
85
139
for (Match match in fileMatches) {
86
- Iterable <String > codes = match
140
+ Iterable <_DiagnosticName > codes = match
87
141
.group (1 )
88
142
.split (',' )
89
- .map ((String code) => code.trim ().toLowerCase ());
143
+ .map ((String code) => _DiagnosticName ( code.trim ().toLowerCase (), - 1 ));
90
144
ignoreInfo._addAllForFile (codes);
91
145
}
92
146
return ignoreInfo;
93
147
}
94
148
}
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