9197b34a0e
Change-Id: I7dc2be03e7266b6ef0a936269a2dbe584096e3db Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104785 Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Dan Rubel <danrubel@google.com>
316 lines
11 KiB
Dart
316 lines
11 KiB
Dart
/**
|
|
* This file contains code to generate scanner and parser message
|
|
* based on the information in pkg/front_end/messages.yaml.
|
|
*
|
|
* For each message in messages.yaml that contains an 'index:' field,
|
|
* this tool generates an error with the name specified by the 'analyzerCode:'
|
|
* field and an entry in the fastaAnalyzerErrorList for that generated error.
|
|
* The text in the 'analyzerCode:' field must contain the name of the class
|
|
* containing the error and the name of the error separated by a `.`
|
|
* (e.g. ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND).
|
|
*
|
|
* It is expected that 'pkg/front_end/tool/fasta generate-messages'
|
|
* has already been successfully run.
|
|
*/
|
|
import 'dart:io';
|
|
|
|
import 'package:analyzer/error/error.dart';
|
|
import 'package:analysis_tool/tools.dart';
|
|
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
|
|
import 'package:front_end/src/testing/package_root.dart' as pkgRoot;
|
|
import 'package:front_end/src/fasta/scanner.dart';
|
|
import 'package:path/path.dart';
|
|
import 'package:yaml/yaml.dart' show loadYaml;
|
|
|
|
main() async {
|
|
String analyzerPkgPath = normalize(join(pkgRoot.packageRoot, 'analyzer'));
|
|
String frontEndPkgPath = normalize(join(pkgRoot.packageRoot, 'front_end'));
|
|
|
|
Map<dynamic, dynamic> messagesYaml = loadYaml(
|
|
new File(join(frontEndPkgPath, 'messages.yaml')).readAsStringSync());
|
|
String errorConverterSource = new File(join(analyzerPkgPath,
|
|
joinAll(posix.split('lib/src/fasta/error_converter.dart'))))
|
|
.readAsStringSync();
|
|
String syntacticErrorsSource = new File(join(analyzerPkgPath,
|
|
joinAll(posix.split('lib/src/dart/error/syntactic_errors.dart'))))
|
|
.readAsStringSync();
|
|
String parserSource = new File(join(frontEndPkgPath,
|
|
joinAll(posix.split('lib/src/fasta/parser/parser.dart'))))
|
|
.readAsStringSync();
|
|
|
|
final codeGenerator = new _SyntacticErrorGenerator(
|
|
messagesYaml, errorConverterSource, syntacticErrorsSource, parserSource);
|
|
|
|
await GeneratedContent.generateAll(analyzerPkgPath, <GeneratedContent>[
|
|
new GeneratedFile('lib/src/dart/error/syntactic_errors.g.dart',
|
|
(String pkgPath) async {
|
|
codeGenerator.generateFormatCode();
|
|
return codeGenerator.out.toString();
|
|
}),
|
|
]);
|
|
|
|
codeGenerator
|
|
..checkForManualChanges()
|
|
..printSummary();
|
|
}
|
|
|
|
const invalidAnalyzerCode = """
|
|
Error: Expected the text in the 'analyzerCode:' field to contain
|
|
the name of the class containing the error
|
|
and the name of the error separated by a `.`
|
|
(e.g. ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND).
|
|
""";
|
|
|
|
const shouldRunFastaGenerateMessagesFirst = """
|
|
Error: After modifying messages.yaml, run this first:
|
|
pkg/front_end/tool/fasta generate-messages
|
|
""";
|
|
|
|
/// Return an entry containing 2 strings,
|
|
/// the name of the class containing the error and the name of the error,
|
|
/// or throw an exception if 'analyzerCode:' field is invalid.
|
|
List<String> nameForEntry(Map entry) {
|
|
final analyzerCode = entry['analyzerCode'];
|
|
if (analyzerCode is String) {
|
|
if (!analyzerCode.startsWith('ParserErrorCode.')) {
|
|
throw invalidAnalyzerCode;
|
|
}
|
|
List<String> name = analyzerCode.split('.');
|
|
if (name.length != 2 || name[1].isEmpty) {
|
|
throw invalidAnalyzerCode;
|
|
}
|
|
return name;
|
|
}
|
|
throw invalidAnalyzerCode;
|
|
}
|
|
|
|
class _SyntacticErrorGenerator {
|
|
final Map messagesYaml;
|
|
final String errorConverterSource;
|
|
final String syntacticErrorsSource;
|
|
final String parserSource;
|
|
final translatedEntries = <Map>[];
|
|
final translatedFastaErrorCodes = new Set<String>();
|
|
final out = new StringBuffer('''
|
|
//
|
|
// THIS FILE IS GENERATED. DO NOT EDIT.
|
|
//
|
|
// Instead modify 'pkg/front_end/messages.yaml' and run
|
|
// 'dart pkg/analyzer/tool/messages/generate.dart' to update.
|
|
|
|
part of 'syntactic_errors.dart';
|
|
|
|
''');
|
|
|
|
_SyntacticErrorGenerator(this.messagesYaml, this.errorConverterSource,
|
|
this.syntacticErrorsSource, this.parserSource);
|
|
|
|
void checkForManualChanges() {
|
|
// Check for ParserErrorCodes that could be removed from
|
|
// error_converter.dart now that those ParserErrorCodes are auto generated.
|
|
int converterCount = 0;
|
|
for (Map entry in translatedEntries) {
|
|
final name = nameForEntry(entry);
|
|
final errorCode = name[1];
|
|
if (errorConverterSource.contains('"$errorCode"')) {
|
|
if (converterCount == 0) {
|
|
print('');
|
|
print('The following ParserErrorCodes could be removed'
|
|
' from error_converter.dart:');
|
|
}
|
|
print(' $errorCode');
|
|
++converterCount;
|
|
}
|
|
}
|
|
if (converterCount > 3) {
|
|
print(' $converterCount total');
|
|
}
|
|
|
|
// Check that the public ParserErrorCodes have been updated
|
|
// to reference the generated codes.
|
|
int publicCount = 0;
|
|
for (Map entry in translatedEntries) {
|
|
final name = nameForEntry(entry);
|
|
final errorCode = name[1];
|
|
if (!syntacticErrorsSource.contains('_$errorCode')) {
|
|
if (publicCount == 0) {
|
|
print('');
|
|
print('The following ParserErrorCodes should be updated'
|
|
' in syntactic_errors.dart');
|
|
print('to reference the associated generated error code:');
|
|
}
|
|
print(' static const ParserErrorCode $errorCode = _$errorCode;');
|
|
++publicCount;
|
|
}
|
|
}
|
|
|
|
// Fail if there are manual changes to be made, but do so
|
|
// in a fire and forget manner so that the files are still generated.
|
|
if (converterCount > 0 || publicCount > 0) {
|
|
print('');
|
|
throw 'Error: missing manual code changes';
|
|
}
|
|
}
|
|
|
|
void generateErrorCodes() {
|
|
final sortedErrorCodes = <String>[];
|
|
final entryMap = <String, Map>{};
|
|
for (var entry in translatedEntries) {
|
|
final name = nameForEntry(entry);
|
|
final errorCode = name[1];
|
|
sortedErrorCodes.add(errorCode);
|
|
if (entryMap[errorCode] == null) {
|
|
entryMap[errorCode] = entry;
|
|
} else {
|
|
throw 'Error: Duplicate error code $errorCode';
|
|
}
|
|
}
|
|
sortedErrorCodes.sort();
|
|
for (var errorCode in sortedErrorCodes) {
|
|
final entry = entryMap[errorCode];
|
|
final className = nameForEntry(entry)[0];
|
|
out.writeln();
|
|
out.writeln('const $className _$errorCode =');
|
|
out.writeln('const $className(');
|
|
out.writeln("'$errorCode',");
|
|
out.writeln('r"${entry['template']}"');
|
|
final tip = entry['tip'];
|
|
if (tip is String) {
|
|
out.writeln(',correction: "$tip"');
|
|
}
|
|
out.writeln(');');
|
|
}
|
|
}
|
|
|
|
void generateFastaAnalyzerErrorCodeList() {
|
|
final sorted = new List<Map>(translatedEntries.length);
|
|
for (var entry in translatedEntries) {
|
|
var index = entry['index'];
|
|
if (index is int && index >= 1 && index <= sorted.length) {
|
|
if (sorted[index - 1] == null) {
|
|
sorted[index - 1] = entry;
|
|
continue;
|
|
}
|
|
}
|
|
throw shouldRunFastaGenerateMessagesFirst;
|
|
}
|
|
out.writeln('final fastaAnalyzerErrorCodes = <ErrorCode>[null,');
|
|
for (var entry in sorted) {
|
|
List<String> name = nameForEntry(entry);
|
|
out.writeln('_${name[1]},');
|
|
}
|
|
out.writeln('];');
|
|
}
|
|
|
|
void generateFormatCode() {
|
|
messagesYaml.forEach((name, entry) {
|
|
if (entry is Map) {
|
|
if (entry['index'] is int && entry['analyzerCode'] is String) {
|
|
translatedFastaErrorCodes.add(name);
|
|
translatedEntries.add(entry);
|
|
}
|
|
}
|
|
});
|
|
generateFastaAnalyzerErrorCodeList();
|
|
generateErrorCodes();
|
|
}
|
|
|
|
void printSummary() {
|
|
// Build a map of error message to ParserErrorCode
|
|
final messageToName = <String, String>{};
|
|
for (ErrorCode errorCode in errorCodeValues) {
|
|
if (errorCode is ParserErrorCode) {
|
|
String message =
|
|
errorCode.message.replaceAll(new RegExp(r'\{\d+\}'), '');
|
|
messageToName[message] = errorCode.name;
|
|
}
|
|
}
|
|
|
|
String messageFromEntryTemplate(Map entry) {
|
|
String template = entry['template'];
|
|
String message = template.replaceAll(new RegExp(r'#\w+'), '');
|
|
return message;
|
|
}
|
|
|
|
// Remove entries that have already been translated
|
|
for (Map entry in translatedEntries) {
|
|
messageToName.remove(messageFromEntryTemplate(entry));
|
|
}
|
|
|
|
// Print the # of autogenerated ParserErrorCodes.
|
|
print('${translatedEntries.length} of ${messageToName.length}'
|
|
' ParserErrorCodes generated.');
|
|
|
|
// List the ParserErrorCodes that could easily be auto generated
|
|
// but have not been already.
|
|
final analyzerToFasta = new Map<String, List<String>>();
|
|
messagesYaml.forEach((fastaName, entry) {
|
|
if (entry is Map) {
|
|
final analyzerName = messageToName[messageFromEntryTemplate(entry)];
|
|
if (analyzerName != null) {
|
|
analyzerToFasta
|
|
.putIfAbsent(analyzerName, () => <String>[])
|
|
.add(fastaName);
|
|
}
|
|
}
|
|
});
|
|
if (analyzerToFasta.isNotEmpty) {
|
|
print('');
|
|
print('The following ParserErrorCodes could be auto generated:');
|
|
for (String analyzerName in analyzerToFasta.keys.toList()..sort()) {
|
|
List<String> fastaNames = analyzerToFasta[analyzerName];
|
|
if (fastaNames.length == 1) {
|
|
print(' $analyzerName = ${fastaNames.first}');
|
|
} else {
|
|
print(' $analyzerName = $fastaNames');
|
|
}
|
|
}
|
|
if (analyzerToFasta.length > 3) {
|
|
print(' ${analyzerToFasta.length} total');
|
|
}
|
|
}
|
|
|
|
// List error codes in the parser that have not been translated.
|
|
final untranslatedFastaErrorCodes = new Set<String>();
|
|
Token token = scanString(parserSource).tokens;
|
|
while (!token.isEof) {
|
|
if (token.isIdentifier) {
|
|
String fastaErrorCode;
|
|
String lexeme = token.lexeme;
|
|
if (lexeme.length > 7) {
|
|
if (lexeme.startsWith('message')) {
|
|
fastaErrorCode = lexeme.substring(7);
|
|
} else if (lexeme.length > 8) {
|
|
if (lexeme.startsWith('template')) {
|
|
fastaErrorCode = lexeme.substring(8);
|
|
}
|
|
}
|
|
}
|
|
if (fastaErrorCode != null &&
|
|
!translatedFastaErrorCodes.contains(fastaErrorCode)) {
|
|
untranslatedFastaErrorCodes.add(fastaErrorCode);
|
|
}
|
|
}
|
|
token = token.next;
|
|
}
|
|
if (untranslatedFastaErrorCodes.isNotEmpty) {
|
|
print('');
|
|
print('The following error codes in the parser are not auto generated:');
|
|
final sorted = untranslatedFastaErrorCodes.toList()..sort();
|
|
for (String fastaErrorCode in sorted) {
|
|
String analyzerCode = '';
|
|
String template = '';
|
|
var entry = messagesYaml[fastaErrorCode];
|
|
if (entry is Map) {
|
|
if (entry['index'] is! int && entry['analyzerCode'] is String) {
|
|
analyzerCode = entry['analyzerCode'];
|
|
template = entry['template'];
|
|
}
|
|
}
|
|
print(' ${fastaErrorCode.padRight(30)} --> $analyzerCode'
|
|
'\n $template');
|
|
}
|
|
}
|
|
}
|
|
}
|