blob: 9dbf07ae2dc7c85f5ffc8f520d6b0755421c976e [file] [log] [blame]
// Copyright (c) 2021, 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 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.
library;
import 'dart:convert';
import 'dart:io';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:analyzer_testing/package_root.dart' as pkg_root;
import 'package:analyzer_utilities/tools.dart';
import 'package:collection/collection.dart';
import 'package:path/path.dart';
import 'error_code_info.dart';
Future<void> main() async {
await GeneratedContent.generateAll(pkg_root.packageRoot, allTargets);
_SyntacticErrorGenerator()
..checkForManualChanges()
..printSummary();
}
/// A list of all targets generated by this code generator.
final List<GeneratedContent> allTargets = _analyzerGeneratedFiles();
/// Generates a list of [GeneratedContent] objects describing all the analyzer
/// files that need to be generated.
List<GeneratedContent> _analyzerGeneratedFiles() {
var classesByFile = <GeneratedErrorCodeFile, List<ErrorClassInfo>>{};
for (var errorClassInfo in errorClasses) {
(classesByFile[errorClassInfo.file] ??= []).add(errorClassInfo);
}
var generatedCodes = <(String, String)>[];
return [
for (var entry in classesByFile.entries)
GeneratedFile(entry.key.path, (pkgRoot) async {
var codeGenerator = _AnalyzerErrorGenerator(
entry.key,
entry.value,
generatedCodes,
);
codeGenerator.generate();
return codeGenerator.out.toString();
}),
GeneratedFile('analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart', (
pkgRoot,
) async {
var codeGenerator = _DiagnosticCodeValuesGenerator(generatedCodes);
codeGenerator.generate();
return codeGenerator.out.toString();
}),
];
}
/// Code generator for analyzer error classes.
class _AnalyzerErrorGenerator {
final GeneratedErrorCodeFile file;
final List<ErrorClassInfo> errorClasses;
final List<(String, String)> generatedCodes;
final StringBuffer out = StringBuffer('''
// Copyright (c) 2021, 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 FILE IS GENERATED. DO NOT EDIT.
//
// Instead modify 'pkg/analyzer/messages.yaml' and run
// 'dart run pkg/analyzer/tool/messages/generate.dart' to update.
// While transitioning `HintCodes` to `WarningCodes`, we refer to deprecated
// codes here.
''');
_AnalyzerErrorGenerator(this.file, this.errorClasses, this.generatedCodes);
void generate() {
out.writeln('// ignore_for_file: deprecated_member_use_from_same_package');
if (file.shouldIgnorePreferSingleQuotes) {
out.writeln('// ignore_for_file: prefer_single_quotes');
}
out.write('''
//
// Generated comments don't quite align with flutter style.
// ignore_for_file: flutter_style_todos
''');
out.writeln();
out.write('''
part of ${json.encode(file.parentLibrary)};
''');
bool shouldGenerateFastaAnalyzerErrorCodes = false;
for (var errorClass in errorClasses) {
if (errorClass.includeCfeMessages) {
shouldGenerateFastaAnalyzerErrorCodes = true;
}
}
if (shouldGenerateFastaAnalyzerErrorCodes) {
out.writeln();
_generateFastaAnalyzerErrorCodeList();
}
for (var errorClass
in errorClasses.toList()..sort((a, b) => a.name.compareTo(b.name))) {
out.writeln();
if (errorClass.comment.isNotEmpty) {
errorClass.comment.trimRight().split('\n').forEach((line) {
out.writeln('/// $line');
});
}
out.write(
'class ${errorClass.name} extends DiagnosticCodeWithExpectedTypes {',
);
var memberAccumulator = MemberAccumulator();
var entries = [
...analyzerMessages[errorClass.name]!.entries,
if (errorClass.includeCfeMessages)
...cfeToAnalyzerErrorCodeTables.analyzerCodeToInfo.entries,
].where((error) => !error.value.isRemoved);
for (var entry in entries) {
var errorName = entry.key;
var errorCodeInfo = entry.value;
try {
if (errorCodeInfo is! AliasErrorCodeInfo &&
errorClass.includeInDiagnosticCodeValues) {
generatedCodes.add((errorClass.name, errorName));
}
errorCodeInfo.toAnalyzerCode(
errorClass,
errorName,
memberAccumulator: memberAccumulator,
);
} catch (e, st) {
Error.throwWithStackTrace(
'While processing ${errorClass.name}.$errorName: $e',
st,
);
}
}
var constructor = StringBuffer();
constructor.writeln(
'/// Initialize a newly created error code to have the given '
'[name].',
);
constructor.writeln(
'const ${errorClass.name}(String name, String problemMessage, {',
);
constructor.writeln('super.correctionMessage,');
constructor.writeln('super.hasPublishedDocs = false,');
constructor.writeln('super.isUnresolvedIdentifier = false,');
constructor.writeln('String? uniqueName,');
constructor.writeln('required super.expectedTypes,');
constructor.writeln('}) : super(');
constructor.writeln('name: name,');
constructor.writeln('problemMessage: problemMessage,');
constructor.writeln(
"uniqueName: '${errorClass.name}.\${uniqueName ?? name}',",
);
constructor.writeln(');');
memberAccumulator.constructors[''] = constructor.toString();
memberAccumulator.accessors['severity'] = '''
@override
DiagnosticSeverity get severity => ${errorClass.severityCode};
''';
memberAccumulator.accessors['type'] = '''
@override
DiagnosticType get type => ${errorClass.typeCode};
''';
memberAccumulator.writeTo(out);
out.writeln('}');
if (literateApiEnabled) {
out.writeln();
_outputDerivedClass(errorClass, withArguments: true);
out.writeln();
_outputDerivedClass(errorClass, withArguments: false);
}
}
}
void _generateFastaAnalyzerErrorCodeList() {
out.writeln('final fastaAnalyzerErrorCodes = <DiagnosticCode?>[');
for (var entry in cfeToAnalyzerErrorCodeTables.indexToInfo) {
var name =
cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode[entry]?.toCamelCase();
out.writeln('${name == null ? 'null' : 'ParserErrorCode.$name'},');
}
out.writeln('];');
}
void _outputDerivedClass(
ErrorClassInfo errorClass, {
required bool withArguments,
}) {
var className =
withArguments
? errorClass.templateName
: errorClass.withoutArgumentsName;
out.writeln('final class $className');
if (withArguments) out.writeln('<T extends Function>');
out.writeln(' extends ${errorClass.name}');
if (!withArguments) out.writeln(' with DiagnosticWithoutArguments');
out.writeln('{');
if (withArguments) {
out.writeln('final T withArguments;');
out.writeln();
}
out.writeln(
'/// Initialize a newly created error code to have the given '
'[name].',
);
out.writeln('const $className(');
out.writeln('super.name,');
out.writeln('super.problemMessage, {');
out.writeln('super.correctionMessage,');
out.writeln('super.hasPublishedDocs = false,');
out.writeln('super.isUnresolvedIdentifier = false,');
out.writeln('super.uniqueName,');
out.writeln('required super.expectedTypes,');
if (withArguments) out.writeln('required this.withArguments,');
out.writeln('});');
out.writeln('}');
}
}
class _DiagnosticCodeValuesGenerator {
final List<(String, String)> generatedCodes;
final StringBuffer out = StringBuffer('''
// Copyright (c) 2014, 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 FILE IS GENERATED. DO NOT EDIT.
//
// Instead modify 'pkg/analyzer/messages.yaml' and run
// 'dart run pkg/analyzer/tool/messages/generate.dart' to update.
// We allow some snake_case and SCREAMING_SNAKE_CASE identifiers in generated
// code, as they match names declared in the source configuration files.
// ignore_for_file: constant_identifier_names
// While transitioning `HintCodes` to `WarningCodes`, we refer to deprecated
// codes here.
// ignore_for_file: deprecated_member_use_from_same_package
''');
_DiagnosticCodeValuesGenerator(this.generatedCodes);
void generate() {
generatedCodes.sortBy((x) => '${x.$1}.${x.$2}');
out.writeln();
out.writeln(r'''
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
import 'package:_fe_analyzer_shared/src/base/errors.dart';
import 'package:analyzer/src/dart/error/ffi_code.dart';
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/manifest/manifest_warning_code.dart';
import 'package:analyzer/src/pubspec/pubspec_warning_code.dart';
''');
out.writeln();
out.writeln(
"@AnalyzerPublicApi(message: 'exported by lib/error/error.dart')",
);
out.writeln('const List<DiagnosticCode> diagnosticCodeValues = [');
for (var (className, errorName) in generatedCodes) {
errorName = errorName.toCamelCase();
out.writeln(' $className.$errorName,');
}
out.writeln('];');
out.writeln();
out.writeln(
"@AnalyzerPublicApi(message: 'exported by lib/error/error.dart')",
);
out.writeln('@Deprecated("Use \'diagnosticCodeValues\' instead")');
out.writeln(
'List<DiagnosticCode> get errorCodeValues => diagnosticCodeValues;',
);
}
}
class _SyntacticErrorGenerator {
final String errorConverterSource;
final String parserSource;
factory _SyntacticErrorGenerator() {
String frontEndSharedPkgPath = normalize(
join(pkg_root.packageRoot, '_fe_analyzer_shared'),
);
String errorConverterSource =
File(
join(
analyzerPkgPath,
joinAll(posix.split('lib/src/fasta/error_converter.dart')),
),
).readAsStringSync();
String parserSource =
File(
join(
frontEndSharedPkgPath,
joinAll(posix.split('lib/src/parser/parser.dart')),
),
).readAsStringSync();
return _SyntacticErrorGenerator._(errorConverterSource, parserSource);
}
_SyntacticErrorGenerator._(this.errorConverterSource, 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 (var errorCode
in cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode.values) {
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');
}
// 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) {
print('');
throw 'Error: missing manual code changes';
}
}
void printSummary() {
// Build a map of error message to ParserErrorCode
var messageToName = <String, String>{};
for (var entry in analyzerMessages['ParserErrorCode']!.entries) {
String message = entry.value.problemMessage.replaceAll(
placeholderPattern,
'',
);
messageToName[message] = entry.key;
}
String messageFromEntryTemplate(ErrorCodeInfo entry) {
String problemMessage = entry.problemMessage;
String message = problemMessage.replaceAll(RegExp(r'#\w+'), '');
return message;
}
// Remove entries that have already been translated
for (ErrorCodeInfo entry
in cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode.keys) {
messageToName.remove(messageFromEntryTemplate(entry));
}
// Print the # of autogenerated ParserErrorCodes.
print(
'${messageToName.length} of '
'${cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode.length} ParserErrorCodes generated.',
);
// List the ParserErrorCodes that could easily be auto generated
// but have not been already.
var analyzerToFasta = <String, List<String>>{};
frontEndMessages.forEach((fastaName, entry) {
var 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');
}
}
}
}