[messages] Write a tool to convert error references to `diag.`.
Adds the file `switch_to_toplevel_diagnostics.dart`. This is a
temporary utility that modifies references to diagnostic constants in
the analyzer and related packages, so that instead of referring to
constants in classes like `CompileTimeErrorCode`, etc., they refer to
top level constants in `diagnostic.dart` files using the import prefix
`diag`.
In a follow-up CL I will run this utility (which will actually make
the changes), and then in a follow-up CL after that, I will delete
it. The reason for splitting this up into multiple CLs is twofold:
- To allow easier code review.
- To make it easier to resolve merge conflicts in the CL that actually
makes the changes, since it can easily be regenerated from scratch
by re-running the tool.
Change-Id: I6a6a6964a6bfdf43227bfe02ba42a673901cacbc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/461582
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer_utilities/pubspec.yaml b/pkg/analyzer_utilities/pubspec.yaml
index d1a2779..137c1b9 100644
--- a/pkg/analyzer_utilities/pubspec.yaml
+++ b/pkg/analyzer_utilities/pubspec.yaml
@@ -21,5 +21,7 @@
# Use 'any' constraints here; we get our versions from the DEPS file.
dev_dependencies:
+ analyzer_plugin: any
+ linter: any
lints: any
test_reflective_loader: any
diff --git a/pkg/analyzer_utilities/tool/messages/switch_to_toplevel_diagnostics.dart b/pkg/analyzer_utilities/tool/messages/switch_to_toplevel_diagnostics.dart
new file mode 100644
index 0000000..c7a5133
--- /dev/null
+++ b/pkg/analyzer_utilities/tool/messages/switch_to_toplevel_diagnostics.dart
@@ -0,0 +1,153 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This is a temporary utility that modifies references to diagnostic constants
+/// in the analyzer and related packages so that instead of referring to
+/// constants in classes like `CompileTimeErrorCode`, etc., they refer to
+/// top level constants in `diagnostic.dart` files using the import prefix
+/// `diag`.
+library;
+
+import 'dart:io';
+
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
+import 'package:analyzer_testing/package_root.dart';
+import 'package:analyzer_utilities/analyzer_messages.dart';
+import 'package:analyzer_utilities/messages.dart';
+import 'package:linter/src/rules.dart' as linter;
+import 'package:path/path.dart';
+
+void main() async {
+ linter.registerLintRules();
+ var provider = PhysicalResourceProvider.INSTANCE;
+ var collection = AnalysisContextCollection(
+ includedPaths: [
+ for (var package in const [
+ 'analyzer',
+ 'analysis_server',
+ 'linter',
+ 'analysis_server_plugin',
+ 'analyzer_plugin',
+ 'analyzer_testing',
+ 'front_end',
+ 'analyzer_cli',
+ ])
+ join(packageRoot, package),
+ ],
+ resourceProvider: provider,
+ );
+ // Use `.single` to make sure that `collection` just contains a single
+ // context. This ensures that the code below will see all the files in the
+ // packages.
+ var context = collection.contexts.single;
+ var changeBuilder = ChangeBuilder(session: context.currentSession);
+ for (var libraryFile in context.contextRoot.analyzedFiles()) {
+ if (!libraryFile.endsWith('.dart')) continue;
+ var fileResult = context.currentSession.getFile(libraryFile) as FileResult;
+ if (fileResult.isLibrary) {
+ var resolvedLibraryResult =
+ (await context.currentSession.getResolvedLibrary(libraryFile))
+ as ResolvedLibraryResult;
+ for (var unit in resolvedLibraryResult.units) {
+ var visitor = _Visitor(
+ libraryUri: resolvedLibraryResult.element.uri,
+ path: unit.path,
+ );
+ unit.unit.accept(visitor);
+ var changes = visitor.changes;
+ if (changes.isNotEmpty) {
+ var s = changes.length == 1 ? '' : 's';
+ print('Found ${changes.length} change$s in ${unit.path}');
+ }
+ await changeBuilder.addDartFileEdit(unit.path, (builder) {
+ for (var change in changes) {
+ change(builder);
+ }
+ });
+ }
+ }
+ }
+ for (var edit in changeBuilder.sourceChange.edits) {
+ var filePath = edit.file;
+ var content = File(filePath).readAsStringSync();
+ var newContent = SourceEdit.applySequence(content, edit.edits);
+ File(filePath).writeAsStringSync(newContent);
+ }
+}
+
+final activeMessagesByCamelCaseName = {
+ for (var messages in diagnosticTables.activeMessagesByPackage.values)
+ for (var message in messages) message.analyzerCode.camelCaseName: message,
+};
+
+final diagnosticClassesByName = {
+ for (var diagnosticClass in diagnosticClasses)
+ diagnosticClass.name: diagnosticClass,
+};
+
+class _Visitor extends RecursiveAstVisitor<void> {
+ final Uri libraryUri;
+ final String path;
+ final List<void Function(DartFileEditBuilder)> changes = [];
+
+ _Visitor({required this.libraryUri, required this.path});
+
+ @override
+ void visitPrefixedIdentifier(PrefixedIdentifier node) {
+ _fixIfNeeded(element: node.identifier.element, prefix: node.prefix);
+ super.visitPrefixedIdentifier(node);
+ }
+
+ @override
+ void visitPropertyAccess(PropertyAccess node) {
+ if (node.operator.type == TokenType.PERIOD) {
+ if (node.target case PrefixedIdentifier prefix) {
+ _fixIfNeeded(element: node.propertyName.element, prefix: prefix);
+ }
+ }
+ super.visitPropertyAccess(node);
+ }
+
+ /// Consider replacing [prefix] with import prefix `diag`.
+ ///
+ /// [element] is the element referred to by the identifier to the right of
+ /// [prefix].
+ void _fixIfNeeded({required Element? element, required Identifier prefix}) {
+ if (element case GetterElement(
+ isStatic: true,
+ name: var messageName?,
+ enclosingElement: ClassElement(name: var className?),
+ )) {
+ if (diagnosticClassesByName[className] case var diagnosticClass?) {
+ if (activeMessagesByCamelCaseName[messageName]
+ case MessageWithAnalyzerCode(:var analyzerCode)
+ when analyzerCode.diagnosticClass == diagnosticClass) {
+ changes.add((builder) {
+ builder.addSimpleReplacement(
+ SourceRange(prefix.offset, prefix.length),
+ 'diag',
+ );
+ var package = diagnosticClass.file.package;
+ builder.importLibrary(
+ Uri.parse(
+ 'package:${package.dirName}/${package.diagnosticPathPart}.dart',
+ ),
+ prefix: 'diag',
+ );
+ });
+ }
+ }
+ }
+ }
+}