Skip to content

Commit 0e9487d

Browse files
stereotype441Commit Queue
authored andcommitted
[messages] Use shared message parsing infrastructure in linter/tool.
Changes the logic in `pkg/linter/tool/machine.dart` and `pkg/linter/tool/messages_info.dart` so that rather than parsing the linter's `messages.yaml` file directly, it uses the shared infrastructure in `pkg/analyzer_utilities` to read it. This required adding a new `LintMessage` class to `pkg/analyzer_utilities`, along with logic to validate and interpret the linter message fields `categories`, `deprecatedDetails`, and `state`. The corresponding logic in `pkg/linter/tool/messages_info.dart` has been simplified accordingly. This paves the way for upcoming `messages.yaml` format changes, ensuring that those format changes won't break the logic in `pkg/linter/tool/messages_info.dart`. Change-Id: I6a6a696445a1fb599970ac4382d458fb31f1944d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/466442 Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
1 parent 31f68af commit 0e9487d

File tree

6 files changed

+215
-217
lines changed

6 files changed

+215
-217
lines changed

pkg/analyzer/test/verify_diagnostics_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:analyzer/error/error.dart';
77
import 'package:analyzer/utilities/package_config_file_builder.dart';
88
import 'package:analyzer_testing/utilities/utilities.dart';
99
import 'package:analyzer_utilities/analyzer_messages.dart';
10+
import 'package:analyzer_utilities/lint_messages.dart';
1011
import 'package:analyzer_utilities/messages.dart';
1112
import 'package:test/test.dart';
1213
import 'package:test_reflective_loader/test_reflective_loader.dart';

pkg/analyzer_utilities/lib/analyzer_messages.dart

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ const linterLintCodeInfo = DiagnosticClassInfo(
110110
/// Decoded messages from the analysis server's `messages.yaml` file.
111111
final List<AnalyzerMessage> analysisServerMessages = decodeAnalyzerMessagesYaml(
112112
analysisServerPkgPath,
113+
decodeMessage: AnalyzerMessage.new,
113114
package: AnalyzerDiagnosticPackage.analysisServer,
114115
);
115116

@@ -121,6 +122,7 @@ final String analysisServerPkgPath = normalize(
121122
/// Decoded messages from the analyzer's `messages.yaml` file.
122123
final List<AnalyzerMessage> analyzerMessages = decodeAnalyzerMessagesYaml(
123124
analyzerPkgPath,
125+
decodeMessage: AnalyzerMessage.new,
124126
package: AnalyzerDiagnosticPackage.analyzer,
125127
);
126128

@@ -132,21 +134,19 @@ final String analyzerPkgPath = normalize(
132134
/// The path to the `linter` package.
133135
final String linterPkgPath = normalize(join(pkg_root.packageRoot, 'linter'));
134136

135-
/// Decoded messages from the linter's `messages.yaml` file.
136-
final List<AnalyzerMessage> lintMessages = decodeAnalyzerMessagesYaml(
137-
linterPkgPath,
138-
allowLinterKeys: true,
139-
package: AnalyzerDiagnosticPackage.linter,
140-
);
141-
142137
/// Decodes a YAML object (in analyzer style `messages.yaml` format) into a list
143138
/// of [AnalyzerMessage]s.
144139
///
145140
/// If [allowLinterKeys], error checking logic will not reject key/value pairs
146141
/// that are used by the linter.
147-
List<AnalyzerMessage> decodeAnalyzerMessagesYaml(
142+
List<M> decodeAnalyzerMessagesYaml<M extends AnalyzerMessage>(
148143
String packagePath, {
149-
bool allowLinterKeys = false,
144+
required M Function(
145+
MessageYaml, {
146+
required AnalyzerCode analyzerCode,
147+
required AnalyzerDiagnosticPackage package,
148+
})
149+
decodeMessage,
150150
required AnalyzerDiagnosticPackage package,
151151
}) {
152152
var path = join(packagePath, 'messages.yaml');
@@ -155,7 +155,7 @@ List<AnalyzerMessage> decodeAnalyzerMessagesYaml(
155155
sourceUrl: Uri.file(path),
156156
);
157157

158-
var result = <AnalyzerMessage>[];
158+
var result = <M>[];
159159
if (yaml is! YamlMap) {
160160
throw LocatedError('root node is not a map', span: yaml.span);
161161
}
@@ -192,15 +192,14 @@ List<AnalyzerMessage> decodeAnalyzerMessagesYaml(
192192
);
193193
}
194194

195-
AnalyzerMessage message = MessageYaml.decode(
195+
M message = MessageYaml.decode(
196196
key: keyNode,
197197
value: diagnosticValue,
198198
decoder: (messageYaml) {
199199
var analyzerCode = AnalyzerCode(snakeCaseName: diagnosticName);
200-
return AnalyzerMessage(
200+
return decodeMessage(
201201
messageYaml,
202202
analyzerCode: analyzerCode,
203-
allowLinterKeys: allowLinterKeys,
204203
package: package,
205204
);
206205
},
@@ -281,9 +280,8 @@ class AliasMessage extends AnalyzerMessage {
281280
super.messageYaml, {
282281
required this.aliasFor,
283282
required super.analyzerCode,
284-
required super.allowLinterKeys,
285283
required super.package,
286-
}) : super._();
284+
}) : super.internal();
287285

288286
String get aliasForClass => aliasFor.split('.').first;
289287

@@ -425,31 +423,27 @@ class AnalyzerMessage extends Message with MessageWithAnalyzerCode {
425423
factory AnalyzerMessage(
426424
MessageYaml messageYaml, {
427425
required AnalyzerCode analyzerCode,
428-
required bool allowLinterKeys,
429426
required AnalyzerDiagnosticPackage package,
430427
}) {
431428
if (messageYaml.getOptionalString('aliasFor') case var aliasFor?) {
432429
return AliasMessage(
433430
messageYaml,
434431
aliasFor: aliasFor,
435432
analyzerCode: analyzerCode,
436-
allowLinterKeys: allowLinterKeys,
437433
package: package,
438434
);
439435
} else {
440-
return AnalyzerMessage._(
436+
return AnalyzerMessage.internal(
441437
messageYaml,
442438
analyzerCode: analyzerCode,
443-
allowLinterKeys: allowLinterKeys,
444439
package: package,
445440
);
446441
}
447442
}
448443

449-
AnalyzerMessage._(
444+
AnalyzerMessage.internal(
450445
MessageYaml messageYaml, {
451446
required this.analyzerCode,
452-
required bool allowLinterKeys,
453447
required this.package,
454448
}) : hasPublishedDocs = messageYaml.getBool('hasPublishedDocs'),
455449
type = messageYaml.get(
@@ -459,10 +453,6 @@ class AnalyzerMessage extends Message with MessageWithAnalyzerCode {
459453
super(messageYaml) {
460454
// Ignore extra keys related to analyzer example-based tests.
461455
messageYaml.allowExtraKeys({'experiment'});
462-
if (allowLinterKeys) {
463-
// Ignore extra keys understood by the linter.
464-
messageYaml.allowExtraKeys({'categories', 'deprecatedDetails', 'state'});
465-
}
466456
}
467457
}
468458

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer_utilities/analyzer_messages.dart';
6+
import 'package:analyzer_utilities/located_error.dart';
7+
import 'package:pub_semver/pub_semver.dart';
8+
import 'package:yaml/yaml.dart';
9+
10+
/// Decoded messages from the linter's `messages.yaml` file.
11+
final List<LintMessage> lintMessages = decodeAnalyzerMessagesYaml(
12+
linterPkgPath,
13+
decodeMessage: LintMessage.new,
14+
package: AnalyzerDiagnosticPackage.linter,
15+
);
16+
17+
enum LintCategory {
18+
binarySize,
19+
brevity,
20+
documentationCommentMaintenance,
21+
effectiveDart,
22+
errorProne,
23+
flutter,
24+
languageFeatureUsage,
25+
memoryLeaks,
26+
nonPerformant,
27+
pub,
28+
publicInterface,
29+
style,
30+
unintentional,
31+
unusedCode,
32+
web;
33+
34+
static final Map<String, LintCategory> _stringToValue = {
35+
for (var value in values) value.name: value,
36+
};
37+
38+
static LintCategory? fromString(String s) => _stringToValue[s];
39+
}
40+
41+
class LintMessage extends AnalyzerMessage {
42+
final Set<LintCategory>? categories;
43+
44+
final String? deprecatedDetails;
45+
46+
final Map<LintStateName, Version>? state;
47+
48+
LintMessage(
49+
super.messageYaml, {
50+
required super.analyzerCode,
51+
required super.package,
52+
}) : categories = messageYaml.get(
53+
'categories',
54+
decode: decodeCategories,
55+
ifAbsent: () => null,
56+
),
57+
deprecatedDetails = messageYaml.getOptionalString('deprecatedDetails'),
58+
state = messageYaml.get(
59+
'state',
60+
decode: decodeState,
61+
ifAbsent: () => null,
62+
),
63+
super.internal();
64+
65+
static Set<LintCategory> decodeCategories(YamlNode node) {
66+
if (node is! YamlList) throw 'Must be a list';
67+
var categoryList = node.nodes.map(
68+
(element) =>
69+
LocatedError.wrap(() => decodeCategory(element), span: element.span),
70+
);
71+
var categorySet = categoryList.toSet();
72+
if (categorySet.length != categoryList.length) {
73+
throw 'Duplicate entries in category list';
74+
}
75+
return categorySet;
76+
}
77+
78+
static LintCategory decodeCategory(YamlNode node) => switch (node) {
79+
YamlScalar(:String value) =>
80+
LintCategory.fromString(value) ?? (throw 'Unknown lint category'),
81+
_ => throw 'Must be a string',
82+
};
83+
84+
static Map<LintStateName, Version> decodeState(YamlNode node) {
85+
if (node is! YamlMap) throw 'Must be a map';
86+
return {
87+
for (var entry in node.nodes.entries)
88+
LocatedError.wrap(
89+
() => decodeStateName(entry.key as YamlScalar),
90+
span: (entry.key as YamlScalar).span,
91+
): LocatedError.wrap(
92+
() => decodeVersion(entry.value),
93+
span: entry.value.span,
94+
),
95+
};
96+
}
97+
98+
static LintStateName decodeStateName(YamlNode node) => switch (node) {
99+
YamlScalar(:String value) =>
100+
LintStateName.fromString(value) ?? (throw 'Unknown lint state name'),
101+
_ => throw 'Must be a string',
102+
};
103+
104+
static Version decodeVersion(YamlNode node) => switch (node) {
105+
YamlScalar(:String value) => Version.parse('$value.0'),
106+
_ => throw 'Must be a string',
107+
};
108+
}
109+
110+
enum LintStateName {
111+
experimental,
112+
stable,
113+
internal,
114+
deprecated,
115+
removed;
116+
117+
static final Map<String, LintStateName> _stringToValue = {
118+
for (var value in values) value.name: value,
119+
};
120+
121+
static LintStateName? fromString(String s) => _stringToValue[s];
122+
}

pkg/analyzer_utilities/lib/messages.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'dart:io';
1111
import 'package:analyzer_testing/package_root.dart' as pkg_root;
1212
import 'package:analyzer_utilities/analyzer_messages.dart';
1313
import 'package:analyzer_utilities/extensions/string.dart';
14+
import 'package:analyzer_utilities/lint_messages.dart';
1415
import 'package:analyzer_utilities/located_error.dart';
1516
import 'package:collection/collection.dart';
1617
import 'package:path/path.dart';

pkg/linter/tool/machine.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ Future<String> getMachineListing(
6363
{
6464
'name': rule.name,
6565
'description': rule.description,
66-
'categories': info.categories.toList(growable: false),
66+
'categories': info.categories
67+
.map((category) => category.name)
68+
.toList(growable: false),
6769
'state': rule.state.label,
6870
'incompatible': rule.incompatibleRules,
6971
'sets': const [],

0 commit comments

Comments
 (0)