| // 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'); |
| } |
| } |
| } |
| } |