- Add VariableSpec class with magnitude field validation - Add Formula class supporting multiple input/output variables - Support d4rt_code as string or object with code field - Add comprehensive tests for parsing and serialization - Fix broken test import in pruebas_d4rt_test.dart Follows README.md format requirements exactly
398 lines
14 KiB
Dart
398 lines
14 KiB
Dart
// 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:_fe_analyzer_shared/src/scanner/scanner.dart';
|
|
import 'package:analyzer_utilities/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(analyzerPkgPath, 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 = <String, List<ErrorClassInfo>>{};
|
|
for (var errorClassInfo in errorClasses) {
|
|
(classesByFile[errorClassInfo.filePath] ??= []).add(errorClassInfo);
|
|
}
|
|
var generatedCodes = <String>[];
|
|
return [
|
|
for (var entry in classesByFile.entries)
|
|
GeneratedFile(entry.key, (String pkgPath) async {
|
|
var codeGenerator =
|
|
_AnalyzerErrorGenerator(entry.value, generatedCodes);
|
|
codeGenerator.generate();
|
|
return codeGenerator.out.toString();
|
|
}),
|
|
GeneratedFile('lib/src/error/error_code_values.g.dart',
|
|
(String pkgPath) async {
|
|
var codeGenerator = _ErrorCodeValuesGenerator(generatedCodes);
|
|
codeGenerator.generate();
|
|
return codeGenerator.out.toString();
|
|
}),
|
|
];
|
|
}
|
|
|
|
/// Code generator for analyzer error classes.
|
|
class _AnalyzerErrorGenerator {
|
|
final List<ErrorClassInfo> errorClasses;
|
|
|
|
final List<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.
|
|
// ignore_for_file: deprecated_member_use_from_same_package
|
|
//
|
|
// Generated comments don't quite align with flutter style.
|
|
// ignore_for_file: flutter_style_todos
|
|
|
|
/// @docImport 'package:analyzer/src/dart/error/syntactic_errors.g.dart';
|
|
/// @docImport 'package:analyzer/src/error/inference_error.dart';
|
|
library;
|
|
''');
|
|
|
|
_AnalyzerErrorGenerator(this.errorClasses, this.generatedCodes);
|
|
|
|
void generate() {
|
|
var imports = {'package:analyzer/error/error.dart'};
|
|
bool shouldGenerateFastaAnalyzerErrorCodes = false;
|
|
for (var errorClass in errorClasses) {
|
|
imports.addAll(errorClass.extraImports);
|
|
if (errorClass.includeCfeMessages) {
|
|
shouldGenerateFastaAnalyzerErrorCodes = true;
|
|
}
|
|
analyzerMessages[errorClass.name]!.forEach((_, errorCodeInfo) {
|
|
if (errorCodeInfo is AliasErrorCodeInfo) {
|
|
imports.add(errorCodeInfo.aliasForFilePath.toPackageAnalyzerUri);
|
|
}
|
|
});
|
|
}
|
|
out.writeln();
|
|
for (var importPath in imports.toList()..sort()) {
|
|
out.writeln("import ${json.encode(importPath)};");
|
|
}
|
|
if (shouldGenerateFastaAnalyzerErrorCodes) {
|
|
out.writeln();
|
|
_generateFastaAnalyzerErrorCodeList();
|
|
}
|
|
for (var errorClass in errorClasses.toList()
|
|
..sort((a, b) => a.name.compareTo(b.name))) {
|
|
out.writeln();
|
|
out.write('class ${errorClass.name} extends ${errorClass.superclass} {');
|
|
var entries = [
|
|
...analyzerMessages[errorClass.name]!.entries,
|
|
if (errorClass.includeCfeMessages)
|
|
...cfeToAnalyzerErrorCodeTables.analyzerCodeToInfo.entries
|
|
].where((error) => !error.value.isRemoved).sortedBy((e) => e.key);
|
|
for (var entry in entries) {
|
|
var errorName = entry.key;
|
|
var errorCodeInfo = entry.value;
|
|
|
|
out.writeln();
|
|
out.write(errorCodeInfo.toAnalyzerComments(indent: ' '));
|
|
var deprecatedMessage = errorCodeInfo.deprecatedMessage;
|
|
if (deprecatedMessage != null) {
|
|
out.writeln(' @Deprecated("$deprecatedMessage")');
|
|
}
|
|
if (errorCodeInfo is AliasErrorCodeInfo) {
|
|
out.writeln(
|
|
' static const ${errorCodeInfo.aliasForClass} $errorName =');
|
|
out.writeln('${errorCodeInfo.aliasFor};');
|
|
} else {
|
|
generatedCodes.add('${errorClass.name}.$errorName');
|
|
out.writeln(' static const ${errorClass.name} $errorName =');
|
|
out.writeln(errorCodeInfo.toAnalyzerCode(errorClass.name, errorName));
|
|
}
|
|
}
|
|
out.writeln();
|
|
out.writeln('/// Initialize a newly created error code to have the given '
|
|
'[name].');
|
|
out.writeln(
|
|
'const ${errorClass.name}(String name, String problemMessage, {');
|
|
out.writeln('super.correctionMessage,');
|
|
out.writeln('super.hasPublishedDocs = false,');
|
|
out.writeln('super.isUnresolvedIdentifier = false,');
|
|
out.writeln('String? uniqueName,');
|
|
out.writeln('}) : super(');
|
|
out.writeln('name: name,');
|
|
out.writeln('problemMessage: problemMessage,');
|
|
out.writeln("uniqueName: '${errorClass.name}.\${uniqueName ?? name}',");
|
|
out.writeln(');');
|
|
out.writeln();
|
|
out.writeln('@override');
|
|
out.writeln('ErrorSeverity get errorSeverity => '
|
|
'${errorClass.severityCode};');
|
|
out.writeln();
|
|
out.writeln('@override');
|
|
out.writeln('ErrorType get type => ${errorClass.typeCode};');
|
|
out.writeln('}');
|
|
}
|
|
}
|
|
|
|
void _generateFastaAnalyzerErrorCodeList() {
|
|
out.writeln('final fastaAnalyzerErrorCodes = <ErrorCode?>[');
|
|
for (var entry in cfeToAnalyzerErrorCodeTables.indexToInfo) {
|
|
var name = cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode[entry];
|
|
out.writeln('${name == null ? 'null' : 'ParserErrorCode.$name'},');
|
|
}
|
|
out.writeln('];');
|
|
}
|
|
}
|
|
|
|
class _ErrorCodeValuesGenerator {
|
|
final List<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
|
|
''');
|
|
|
|
_ErrorCodeValuesGenerator(this.generatedCodes);
|
|
|
|
void generate() {
|
|
// The scanner error codes are not yet being generated, so we need to add
|
|
// them to the list explicitly.
|
|
generatedCodes.addAll([
|
|
'ScannerErrorCode.EXPECTED_TOKEN',
|
|
'ScannerErrorCode.ILLEGAL_CHARACTER',
|
|
'ScannerErrorCode.MISSING_DIGIT',
|
|
'ScannerErrorCode.MISSING_HEX_DIGIT',
|
|
'ScannerErrorCode.MISSING_IDENTIFIER',
|
|
'ScannerErrorCode.MISSING_QUOTE',
|
|
'ScannerErrorCode.UNABLE_GET_CONTENT',
|
|
'ScannerErrorCode.UNEXPECTED_DOLLAR_IN_STRING',
|
|
'ScannerErrorCode.UNSUPPORTED_OPERATOR',
|
|
'ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT',
|
|
'ScannerErrorCode.UNTERMINATED_STRING_LITERAL',
|
|
'TodoCode.TODO',
|
|
'TodoCode.FIXME',
|
|
'TodoCode.HACK',
|
|
'TodoCode.UNDONE',
|
|
]);
|
|
generatedCodes.sort();
|
|
|
|
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<ErrorCode> errorCodeValues = [');
|
|
for (var name in generatedCodes) {
|
|
out.writeln(' $name,');
|
|
}
|
|
out.writeln('];');
|
|
}
|
|
}
|
|
|
|
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(RegExp(r'\{\d+\}'), '');
|
|
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');
|
|
}
|
|
}
|
|
|
|
// List error codes in the parser that have not been translated.
|
|
var untranslatedFastaErrorCodes = <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 &&
|
|
cfeToAnalyzerErrorCodeTables.frontEndCodeToInfo[fastaErrorCode] ==
|
|
null) {
|
|
untranslatedFastaErrorCodes.add(fastaErrorCode);
|
|
}
|
|
}
|
|
token = token.next!;
|
|
}
|
|
if (untranslatedFastaErrorCodes.isNotEmpty) {
|
|
print('');
|
|
print('The following error codes in the parser are not auto generated:');
|
|
var sorted = untranslatedFastaErrorCodes.toList()..sort();
|
|
for (String fastaErrorCode in sorted) {
|
|
String analyzerCode = '';
|
|
String problemMessage = '';
|
|
var entry = frontEndMessages[fastaErrorCode];
|
|
if (entry != null) {
|
|
// TODO(paulberry): handle multiple analyzer codes
|
|
if (entry.index == null && entry.analyzerCode.length == 1) {
|
|
analyzerCode = entry.analyzerCode.single;
|
|
problemMessage = entry.problemMessage;
|
|
}
|
|
}
|
|
print(' ${fastaErrorCode.padRight(30)} --> $analyzerCode'
|
|
'\n $problemMessage');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension on String {
|
|
String get toPackageAnalyzerUri =>
|
|
replaceFirst(RegExp('^lib/'), 'package:analyzer/');
|
|
}
|