[messages] Add temporary tool use_prefixed_import_instead_of_code.dart.

This tool will be used to automatically migrate the code in
`pkg/front_end` (and related packages) from using the old
`codes_generated.dart` and `cfe_codes_generated.dart` files to the new
`diagnostic.g.dart` files.

Change-Id: I6a6a6964b720180b9cf7a3cadf94fa70fe42c1c2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475927
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry
2026-01-27 17:19:59 -08:00
committed by Commit Queue
parent be20f9421c
commit b7eaea96dd
2 changed files with 178 additions and 0 deletions
+5
View File
@@ -441,6 +441,9 @@ class DiagnosticTables {
final Map<String, MessageWithAnalyzerCode> diagnosticsByAnalyzerUniqueName =
{};
/// Map from [DiagnosticCodeName.pascalCaseName] to front end diagnostic.
final Map<String, CfeStyleMessage> frontEndDiagnosticsByPascalCaseName = {};
DiagnosticTables._(List<Message> messages) {
var frontEndCodeDuplicateChecker = _DuplicateChecker<DiagnosticCodeName>(
kind: 'Front end code',
@@ -458,6 +461,8 @@ class DiagnosticTables {
var frontEndCode = message.frontEndCode;
frontEndCodeDuplicateChecker[frontEndCode] = message;
sortedFrontEndDiagnostics.add(message);
frontEndDiagnosticsByPascalCaseName[frontEndCode.pascalCaseName] =
message;
}
if (message is SharedMessage) {
sortedSharedDiagnostics.add(message);
@@ -0,0 +1,173 @@
// Copyright (c) 2026, 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.
import 'dart:io';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.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';
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/messages.dart';
import 'package:path/path.dart';
const packages = {
'_fe_analyzer_shared',
'_js_interop_checks',
'dart2wasm',
'front_end',
'vm',
};
void main() async {
var provider = PhysicalResourceProvider.INSTANCE;
var collection = AnalysisContextCollection(
includedPaths: [for (var package in packages) 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;
var relativePath = relative(libraryFile, from: packageRoot);
var package = split(relativePath).first;
if (!packages.contains(package)) {
throw StateError('Unexpected package $package');
}
for (var unit in resolvedLibraryResult.units) {
var visitor = _Visitor(package: package);
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);
}
// Attempting to format the whole file can result in conflicts
// because any added imports will be applied after formatting. So
// just format the portion of the file starting at the first
// declaration.
if (unit.unit.declarations case [var first, ...]) {
var offset = first.offset;
builder.format(SourceRange(offset, unit.content.length - offset));
}
});
}
}
}
}
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);
}
}
class _Visitor extends RecursiveAstVisitor<void> {
final String package;
final List<void Function(DartFileEditBuilder)> changes = [];
/// While visiting a show/hide combinator, this set gathers all the simple
/// identifiers that can be safely removed from the combinator.
Set<SimpleIdentifier>? _namesToRemoveFromCombinator;
_Visitor({required this.package});
@override
void visitShowCombinator(ShowCombinator node) {
var namesToRemoveFromCombinator = _namesToRemoveFromCombinator = {};
super.visitShowCombinator(node);
_namesToRemoveFromCombinator = null;
if (namesToRemoveFromCombinator.isEmpty) return;
var shownNames = node.shownNames;
if (namesToRemoveFromCombinator.length == shownNames.length) {
// The whole import can be removed.
changes.add((builder) {
var importDirective = node.parent as ImportDirective;
builder.addDeletion(
SourceRange(importDirective.offset, importDirective.length),
);
});
} else {
// Each name has to be removed along with an adjacent `,`. For names
// at the end of the list, remove the preceding `,`. For all other names,
// remove the following `,`.
var removePrecedingComma = true;
for (var i = shownNames.length; i-- > 0;) {
if (namesToRemoveFromCombinator.contains(shownNames[i])) {
int offset;
int end;
if (removePrecedingComma) {
offset = shownNames[i - 1].end;
end = shownNames[i].end;
} else {
offset = shownNames[i].offset;
end = shownNames[i + 1].offset;
}
changes.add((builder) {
builder.addDeletion(SourceRange(offset, end - offset));
});
} else {
removePrecedingComma = false;
}
}
}
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (!node.name.startsWith('code')) return;
var pascalCaseName = node.name.substring('code'.length);
var element = node.element;
if (element is GetterElement) element = element.variable;
if (element is! TopLevelVariableElement) return;
var message =
diagnosticTables.frontEndDiagnosticsByPascalCaseName[pascalCaseName];
if (message == null) return;
var replacementText = 'diag.${message.frontEndCode.camelCaseName}';
Identifier nodeToReplace;
if (node.parent case PrefixedIdentifier parent
when parent.prefix.element is PrefixElement) {
nodeToReplace = parent;
} else {
nodeToReplace = node;
}
if (node.parent is Combinator) {
_namesToRemoveFromCombinator!.add(node);
} else {
changes.add((builder) {
var offset = nodeToReplace.offset;
builder.addSimpleReplacement(
SourceRange(offset, nodeToReplace.end - offset),
replacementText,
);
builder.importLibrary(
Uri.parse(
package == '_fe_analyzer_shared'
? 'package:_fe_analyzer_shared/src/messages/diagnostic.dart'
: 'package:front_end/src/codes/diagnostic.dart',
),
prefix: 'diag',
);
});
}
}
}