Skip to content

Commit 052e03c

Browse files
srawlinsCommit Queue
authored andcommitted
DAS plugins: Support plugin-namespaced inline ignore comments
Fixes #59647 Change-Id: I303c83c3a486d6eb495e890007127215357e2864 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406686 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 00e5314 commit 052e03c

File tree

6 files changed

+143
-20
lines changed

6 files changed

+143
-20
lines changed

pkg/analysis_server_plugin/lib/src/plugin_server.dart

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import 'package:analyzer/src/dart/analysis/byte_store.dart';
2626
import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
2727
import 'package:analyzer/src/dart/analysis/session.dart';
2828
import 'package:analyzer/src/dart/element/type_system.dart';
29+
import 'package:analyzer/src/ignore_comments/ignore_info.dart';
2930
import 'package:analyzer/src/lint/linter.dart';
3031
import 'package:analyzer/src/lint/linter_visitor.dart';
3132
import 'package:analyzer/src/lint/registry.dart';
@@ -217,7 +218,7 @@ class PluginServer {
217218
}) async {
218219
var file = _resourceProvider.getFile(path);
219220
var analysisOptions = analysisContext.getAnalysisOptionsForFile(file);
220-
var diagnostics = await _computeLints(
221+
var diagnostics = await _computeDiagnostics(
221222
analysisContext,
222223
path,
223224
analysisOptions: analysisOptions as AnalysisOptionsImpl,
@@ -244,19 +245,19 @@ class PluginServer {
244245
}
245246
}
246247

247-
Future<List<protocol.AnalysisError>> _computeLints(
248+
Future<List<protocol.AnalysisError>> _computeDiagnostics(
248249
AnalysisContext analysisContext,
249250
String path, {
250251
required AnalysisOptionsImpl analysisOptions,
251252
}) async {
252253
var libraryResult =
253254
await analysisContext.currentSession.getResolvedLibrary(path);
254255
if (libraryResult is! ResolvedLibraryResult) {
255-
return [];
256+
return const [];
256257
}
257258
var unitResult = await analysisContext.currentSession.getResolvedUnit(path);
258259
if (unitResult is! ResolvedUnitResult) {
259-
return [];
260+
return const [];
260261
}
261262
var listener = RecordingErrorListener();
262263
var errorReporter = ErrorReporter(
@@ -293,6 +294,9 @@ class PluginServer {
293294
null,
294295
);
295296

297+
// A mapping from each lint and warning code to its corresponding plugin.
298+
var pluginCodeMapping = <String, String>{};
299+
296300
for (var configuration in analysisOptions.pluginConfigurations) {
297301
if (!configuration.isEnabled) continue;
298302
// TODO(srawlins): Namespace rules by their plugin, to avoid collisions.
@@ -304,15 +308,33 @@ class PluginServer {
304308
// `benchhmark.dart` script does.
305309
rule.registerNodeProcessors(nodeRegistry, context);
306310
}
311+
for (var code in rules.expand((r) => r.lintCodes)) {
312+
var existingPlugin = pluginCodeMapping[code.name];
313+
if (existingPlugin == null) {
314+
pluginCodeMapping[code.name] = configuration.name;
315+
}
316+
}
307317
}
308318

309319
context.currentUnit = currentUnit;
310320
currentUnit.unit.accept(
311321
AnalysisRuleVisitor(nodeRegistry, shouldPropagateExceptions: true));
322+
323+
var ignoreInfo = IgnoreInfo.forDart(unitResult.unit, unitResult.content);
324+
var errors = listener.errors.where((e) {
325+
var pluginName = pluginCodeMapping[e.errorCode.name];
326+
if (pluginName == null) {
327+
// If [e] is somehow not mapped, something is wrong; but don't mark it
328+
// as ignored.
329+
return true;
330+
}
331+
return !ignoreInfo.ignored(e, pluginName: pluginName);
332+
});
333+
312334
// The list of the `AnalysisError`s and their associated
313335
// `protocol.AnalysisError`s.
314336
var errorsAndProtocolErrors = [
315-
for (var e in listener.errors)
337+
for (var e in errors)
316338
(
317339
error: e,
318340
protocolError: protocol.AnalysisError(

pkg/analysis_server_plugin/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ environment:
77
sdk: '>=3.3.0 <4.0.0'
88

99
dependencies:
10-
analyzer: ^7.2.0
10+
analyzer: ^7.3.0
1111
analyzer_plugin: ^0.13.0
1212
meta: ^1.16.0
1313

pkg/analysis_server_plugin/test/src/plugin_server_test.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,33 @@ class PluginServerTest extends PluginServerTestBase {
4848
await startPlugin();
4949
}
5050

51+
Future<void> test_diagnosticsCanBeIgnored() async {
52+
writeAnalysisOptionsWithPlugin();
53+
newFile(filePath, '''
54+
// ignore: no_literals/no_bools
55+
bool b = false;
56+
''');
57+
await channel
58+
.sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
59+
var paramsQueue = _analysisErrorsParams;
60+
var params = await paramsQueue.next;
61+
expect(params.errors, isEmpty);
62+
}
63+
64+
Future<void> test_diagnosticsCanBeIgnored_forFile() async {
65+
writeAnalysisOptionsWithPlugin();
66+
newFile(filePath, '''
67+
bool b = false;
68+
69+
// ignore_for_file: no_literals/no_bools
70+
''');
71+
await channel
72+
.sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
73+
var paramsQueue = _analysisErrorsParams;
74+
var params = await paramsQueue.next;
75+
expect(params.errors, isEmpty);
76+
}
77+
5178
Future<void> test_handleAnalysisSetContextRoots() async {
5279
writeAnalysisOptionsWithPlugin();
5380
newFile(filePath, 'bool b = false;');

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

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,26 @@ class IgnoredDiagnosticComment implements IgnoredElement {
1818
IgnoredDiagnosticComment(this.text, this.offset);
1919

2020
@override
21-
bool matches(ErrorCode errorCode) => false;
21+
bool _matches(ErrorCode errorCode, {String? pluginName}) => false;
2222
}
2323

2424
/// The name and location of a diagnostic name in an ignore comment.
2525
class IgnoredDiagnosticName implements IgnoredElement {
2626
/// The name of the diagnostic being ignored.
2727
final String name;
2828

29+
final String? pluginName;
30+
2931
final int offset;
3032

31-
IgnoredDiagnosticName(String name, this.offset) : name = name.toLowerCase();
33+
IgnoredDiagnosticName(String name, this.offset, {this.pluginName})
34+
: name = name.toLowerCase();
3235

3336
@override
34-
bool matches(ErrorCode errorCode) {
37+
bool _matches(ErrorCode errorCode, {String? pluginName}) {
38+
if (this.pluginName != pluginName) {
39+
return false;
40+
}
3541
if (name == errorCode.name.toLowerCase()) {
3642
return true;
3743
}
@@ -56,7 +62,8 @@ class IgnoredDiagnosticType implements IgnoredElement {
5662
: type = type.toLowerCase();
5763

5864
@override
59-
bool matches(ErrorCode errorCode) {
65+
bool _matches(ErrorCode errorCode, {String? pluginName}) {
66+
// Ignore 'pluginName'; it is irrelevant in an IgnoredDiagnosticType.
6067
return switch (errorCode.type) {
6168
ErrorType.HINT => type == 'hint',
6269
ErrorType.LINT => type == 'lint',
@@ -69,7 +76,7 @@ class IgnoredDiagnosticType implements IgnoredElement {
6976

7077
sealed class IgnoredElement {
7178
/// Returns whether this matches the given [errorCode].
72-
bool matches(ErrorCode errorCode);
79+
bool _matches(ErrorCode errorCode, {String? pluginName});
7380
}
7481

7582
/// Information about analysis `//ignore:` and `//ignore_for_file:` comments
@@ -178,24 +185,27 @@ class IgnoreInfo {
178185
return ignoredOnLine;
179186
}
180187

181-
bool ignored(AnalysisError error) {
188+
/// Whether [error] is ignored via an inline "ignore" comment.
189+
bool ignored(AnalysisError error, {String? pluginName}) {
182190
var line = _lineInfo.getLocation(error.offset).lineNumber;
183-
return ignoredAt(error.errorCode, line);
191+
return _ignoredAt(error.errorCode, line, pluginName: pluginName);
184192
}
185193

186194
/// Returns whether the [errorCode] is ignored at the given [line].
187-
bool ignoredAt(ErrorCode errorCode, int line) {
195+
bool _ignoredAt(ErrorCode errorCode, int line, {String? pluginName}) {
188196
var ignoredDiagnostics = _ignoredOnLine[line];
189197
if (ignoredForFile.isEmpty && ignoredDiagnostics == null) {
190198
return false;
191199
}
192-
if (ignoredForFile.any((name) => name.matches(errorCode))) {
200+
if (ignoredForFile
201+
.any((name) => name._matches(errorCode, pluginName: pluginName))) {
193202
return true;
194203
}
195204
if (ignoredDiagnostics == null) {
196205
return false;
197206
}
198-
return ignoredDiagnostics.any((name) => name.matches(errorCode));
207+
return ignoredDiagnostics
208+
.any((name) => name._matches(errorCode, pluginName: pluginName));
199209
}
200210
}
201211

@@ -259,8 +269,8 @@ extension CommentTokenExtension on CommentToken {
259269
// Parse diagnostic type.
260270
skipPastWhitespace();
261271
if (offset == lexeme.length) return;
262-
var equalSign = lexeme.codeUnitAt(offset);
263-
if (equalSign != 0x3D) return;
272+
var nextChar = lexeme.codeUnitAt(offset);
273+
if (!nextChar.isEqual) return;
264274
offset++;
265275
skipPastWhitespace();
266276
if (offset == lexeme.length) return;
@@ -293,6 +303,30 @@ extension CommentTokenExtension on CommentToken {
293303
yield IgnoredDiagnosticType(
294304
type, this.offset + wordOffset, offset - wordOffset);
295305
} else {
306+
String? pluginName;
307+
if (offset < lexeme.length) {
308+
var nextChar = lexeme.codeUnitAt(offset);
309+
if (nextChar.isSlash) {
310+
// We may be looking at a plugin-name-prefixed code, like
311+
// 'plugin_one/foo'.
312+
pluginName = word;
313+
offset++;
314+
if (offset == lexeme.length) return;
315+
var nameOffset = offset;
316+
readWord();
317+
word = lexeme.substring(nameOffset, offset);
318+
if (nameOffset == offset) {
319+
// There is a non-word (other characters) at `offset`.
320+
if (hasIgnoredElements) {
321+
yield IgnoredDiagnosticComment(
322+
lexeme.substring(offset),
323+
this.offset + nameOffset,
324+
);
325+
}
326+
return;
327+
}
328+
}
329+
}
296330
if (offset < lexeme.length) {
297331
var nextChar = lexeme.codeUnitAt(offset);
298332
if (!nextChar.isSpace && !nextChar.isComma) {
@@ -306,7 +340,8 @@ extension CommentTokenExtension on CommentToken {
306340
}
307341
}
308342
hasIgnoredElements = true;
309-
yield IgnoredDiagnosticName(word, this.offset + wordOffset);
343+
yield IgnoredDiagnosticName(word, this.offset + wordOffset,
344+
pluginName: pluginName);
310345
}
311346

312347
if (offset == lexeme.length) return;

pkg/analyzer/lib/src/utilities/extensions/string.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ extension IntExtension on int {
1111
/// character.
1212
bool get isEOL => this == 0x0D || this == 0x0A;
1313

14+
bool get isEqual => this == 0x3D;
15+
1416
bool get isLetter =>
1517
(this >= 0x41 && this <= 0x5A) || (this >= 0x61 && this <= 0x7A);
1618

1719
bool get isLetterOrDigit => isLetter || isDigit;
1820

1921
bool get isLetterOrDigitOrUnderscore => isLetter || isDigit || this == 0x5F;
2022

23+
bool get isSlash => this == 0x2F;
24+
2125
/// Whether this, as an ASCII character, is a space or tab character.
2226
bool get isSpace => this == 0x20 || this == 0x09;
2327

pkg/analyzer/test/src/ignore_comments/ignore_info_test.dart

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class IgnoreInfoTest extends PubPackageResolutionTest {
3535
expect(ignoredElements, isEmpty);
3636
}
3737

38+
test_noIgnores_trailingSlash() async {
39+
var ignoredElements = await _parseIgnoredElements('// ignore: /');
40+
expect(ignoredElements, isEmpty);
41+
}
42+
3843
test_noIgnores_trailingWhitespace() async {
3944
var ignoredElements = await _parseIgnoredElements('// ignore: ');
4045
expect(ignoredElements, isEmpty);
@@ -59,6 +64,24 @@ class IgnoreInfoTest extends PubPackageResolutionTest {
5964
_expectIgnoredName(ignoredElements[0], name: 'foo', offset: 10);
6065
}
6166

67+
test_pluginName() async {
68+
var ignoredElements =
69+
await _parseIgnoredElements('// ignore: plugin_one/foo');
70+
expect(ignoredElements, hasLength(1));
71+
_expectIgnoredName(ignoredElements[0],
72+
name: 'foo', offset: 11, pluginName: 'plugin_one');
73+
}
74+
75+
test_pluginName_multiple() async {
76+
var ignoredElements = await _parseIgnoredElements(
77+
'// ignore: plugin_one/foo, plugin_two/bar');
78+
expect(ignoredElements, hasLength(2));
79+
_expectIgnoredName(ignoredElements[0],
80+
name: 'foo', offset: 11, pluginName: 'plugin_one');
81+
_expectIgnoredName(ignoredElements[1],
82+
name: 'bar', offset: 27, pluginName: 'plugin_two');
83+
}
84+
6285
test_trailingComma() async {
6386
var ignoredElements = await _parseIgnoredElements('// ignore: foo,');
6487
expect(ignoredElements, hasLength(1));
@@ -78,6 +101,16 @@ class IgnoreInfoTest extends PubPackageResolutionTest {
78101
_expectIgnoredName(ignoredElements[0], name: 'foo', offset: 11);
79102
}
80103

104+
test_trailingSlash() async {
105+
var ignoredElements = await _parseIgnoredElements('// ignore: foo/');
106+
expect(ignoredElements, isEmpty);
107+
}
108+
109+
test_trailingSlashAndSpace() async {
110+
var ignoredElements = await _parseIgnoredElements('// ignore: foo/ ');
111+
expect(ignoredElements, isEmpty);
112+
}
113+
81114
test_trailingSpace() async {
82115
var ignoredElements = await _parseIgnoredElements('// ignore: foo ');
83116
expect(ignoredElements, hasLength(1));
@@ -135,12 +168,14 @@ class IgnoreInfoTest extends PubPackageResolutionTest {
135168
IgnoredElement element, {
136169
required String name,
137170
required int offset,
171+
String? pluginName,
138172
}) =>
139173
expect(
140174
element,
141175
isA<IgnoredDiagnosticName>()
142176
.having((e) => e.name, 'name', name)
143-
.having((e) => e.offset, 'offset', offset),
177+
.having((e) => e.offset, 'offset', offset)
178+
.having((e) => e.pluginName, 'pluginName', pluginName),
144179
);
145180

146181
void _expectIgnoredType(

0 commit comments

Comments
 (0)