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' ;
77import 'package:analyzer/source/line_info.dart' ;
8+ import 'package:analyzer/src/dart/ast/token.dart' ;
89import '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