mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[CFE] Add pkg/front_end/tool/_fasta/generate_messages_failsafe.dart
Often when rebasing I get conflicts in pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart which is a generated file. This should generally be fine, because I can just regenerate it. Except that the code regenerating it uses the very file, so I can't run it. Until now I've had to revert the changes in the file and then run the generate script. This CL adds a "failsafe" version that doesn't use the file, but doesn't directly format it either, then calls the original script which will also format it. Call with something like ``` out/ReleaseX64/dart pkg/front_end/tool/_fasta/generate_messages_failsafe.dart ``` Change-Id: Ic395c7b378b245b41b6fe194affaa6c64ca482db Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349481 Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Jens Johansen <jensj@google.com>
This commit is contained in:
parent
7512050101
commit
f908dc692f
|
@ -306,6 +306,7 @@ extern
|
|||
f\u
|
||||
fa
|
||||
fac
|
||||
failsafe
|
||||
faking
|
||||
falling
|
||||
faults
|
||||
|
@ -835,6 +836,7 @@ walt
|
|||
warmup
|
||||
week
|
||||
weekly
|
||||
went
|
||||
wherever
|
||||
whiskers
|
||||
wildcards
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
// 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' show File, exitCode;
|
||||
|
||||
import "package:_fe_analyzer_shared/src/messages/severity.dart"
|
||||
show severityEnumNames;
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:dart_style/dart_style.dart' show DartFormatter;
|
||||
|
||||
import 'package:yaml/yaml.dart' show loadYaml;
|
||||
|
||||
import '../../test/utils/io_utils.dart' show computeRepoDirUri;
|
||||
import 'generate_messages_lib.dart';
|
||||
|
||||
export 'generate_messages_lib.dart';
|
||||
|
||||
void main(List<String> arguments) {
|
||||
final Uri repoDir = computeRepoDirUri();
|
||||
|
@ -28,451 +26,7 @@ void main(List<String> arguments) {
|
|||
}
|
||||
}
|
||||
|
||||
Uri computeSharedGeneratedFile(Uri repoDir) {
|
||||
return repoDir
|
||||
.resolve("pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart");
|
||||
}
|
||||
|
||||
Uri computeCfeGeneratedFile(Uri repoDir) {
|
||||
return repoDir
|
||||
.resolve("pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart");
|
||||
}
|
||||
|
||||
class Messages {
|
||||
final String sharedMessages;
|
||||
final String cfeMessages;
|
||||
|
||||
Messages(this.sharedMessages, this.cfeMessages);
|
||||
}
|
||||
|
||||
Messages generateMessagesFiles(Uri repoDir) {
|
||||
Uri messagesFile = repoDir.resolve("pkg/front_end/messages.yaml");
|
||||
Map<dynamic, dynamic> yaml =
|
||||
loadYaml(new File.fromUri(messagesFile).readAsStringSync());
|
||||
StringBuffer sharedMessages = new StringBuffer();
|
||||
StringBuffer cfeMessages = new StringBuffer();
|
||||
|
||||
const String preamble1 = """
|
||||
// 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.
|
||||
|
||||
""";
|
||||
|
||||
const String preamble2 = """
|
||||
|
||||
// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
|
||||
//
|
||||
// Instead modify 'pkg/front_end/messages.yaml' and run
|
||||
// 'pkg/front_end/tool/fasta generate-messages' to update.
|
||||
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
""";
|
||||
|
||||
sharedMessages.writeln(preamble1);
|
||||
sharedMessages.writeln(preamble2);
|
||||
sharedMessages.writeln("""
|
||||
part of _fe_analyzer_shared.messages.codes;
|
||||
""");
|
||||
|
||||
cfeMessages.writeln(preamble1);
|
||||
cfeMessages.writeln("""
|
||||
|
||||
""");
|
||||
cfeMessages.writeln(preamble2);
|
||||
cfeMessages.writeln("""
|
||||
part of fasta.codes;
|
||||
""");
|
||||
|
||||
bool hasError = false;
|
||||
int largestIndex = 0;
|
||||
final indexNameMap = new Map<int, String>();
|
||||
|
||||
List<String> keys = yaml.keys.cast<String>().toList()..sort();
|
||||
for (String name in keys) {
|
||||
var description = yaml[name];
|
||||
while (description is String) {
|
||||
description = yaml[description];
|
||||
}
|
||||
Map<dynamic, dynamic>? map = description;
|
||||
if (map == null) {
|
||||
throw "No 'problemMessage:' in key $name.";
|
||||
}
|
||||
var index = map['index'];
|
||||
if (index != null) {
|
||||
if (index is! int || index < 1) {
|
||||
print('Error: Expected positive int for "index:" field in $name,'
|
||||
' but found $index');
|
||||
hasError = true;
|
||||
index = -1;
|
||||
// Continue looking for other problems.
|
||||
} else {
|
||||
String? otherName = indexNameMap[index];
|
||||
if (otherName != null) {
|
||||
print('Error: The "index:" field must be unique, '
|
||||
'but is the same for $otherName and $name');
|
||||
hasError = true;
|
||||
// Continue looking for other problems.
|
||||
} else {
|
||||
indexNameMap[index] = name;
|
||||
if (largestIndex < index) {
|
||||
largestIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Template template = compileTemplate(name, index, map['problemMessage'],
|
||||
map['correctionMessage'], map['analyzerCode'], map['severity']);
|
||||
if (template.isShared) {
|
||||
sharedMessages.writeln(template.text);
|
||||
} else {
|
||||
cfeMessages.writeln(template.text);
|
||||
}
|
||||
}
|
||||
if (largestIndex > indexNameMap.length) {
|
||||
print('Error: The "index:" field values should be unique, consecutive'
|
||||
' whole numbers starting with 1.');
|
||||
hasError = true;
|
||||
// Fall through to print more information.
|
||||
}
|
||||
if (hasError) {
|
||||
exitCode = 1;
|
||||
print('The largest index is $largestIndex');
|
||||
final sortedIndices = indexNameMap.keys.toList()..sort();
|
||||
int nextAvailableIndex = largestIndex + 1;
|
||||
for (int index = 1; index <= sortedIndices.length; ++index) {
|
||||
if (sortedIndices[index - 1] != index) {
|
||||
nextAvailableIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
print('The next available index is ${nextAvailableIndex}');
|
||||
return new Messages('', '');
|
||||
}
|
||||
|
||||
return new Messages(new DartFormatter().format("$sharedMessages"),
|
||||
new DartFormatter().format("$cfeMessages"));
|
||||
}
|
||||
|
||||
final RegExp placeholderPattern =
|
||||
new RegExp("#\([-a-zA-Z0-9_]+\)(?:%\([0-9]*\)\.\([0-9]+\))?");
|
||||
|
||||
class Template {
|
||||
final String text;
|
||||
final isShared;
|
||||
|
||||
Template(this.text, {this.isShared}) : assert(isShared != null);
|
||||
}
|
||||
|
||||
Template compileTemplate(String name, int? index, String? problemMessage,
|
||||
String? correctionMessage, Object? analyzerCode, String? severity) {
|
||||
if (problemMessage == null) {
|
||||
print('Error: missing problemMessage for message: $name');
|
||||
exitCode = 1;
|
||||
return new Template('', isShared: true);
|
||||
}
|
||||
// Remove trailing whitespace. This is necessary for templates defined with
|
||||
// `|` (verbatim) as they always contain a trailing newline that we don't
|
||||
// want.
|
||||
problemMessage = problemMessage.trimRight();
|
||||
var parameters = new Set<String>();
|
||||
var conversions = new Set<String>();
|
||||
var conversions2 = new Set<String>();
|
||||
var arguments = new Set<String>();
|
||||
bool hasLabeler = false;
|
||||
bool canBeShared = true;
|
||||
void ensureLabeler() {
|
||||
if (hasLabeler) return;
|
||||
conversions
|
||||
.add("TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);");
|
||||
hasLabeler = true;
|
||||
canBeShared = false;
|
||||
}
|
||||
|
||||
for (Match match in placeholderPattern
|
||||
.allMatches("$problemMessage\n${correctionMessage ?? ''}")) {
|
||||
String name = match[1]!;
|
||||
String? padding = match[2];
|
||||
String? fractionDigits = match[3];
|
||||
|
||||
String format(String name) {
|
||||
String conversion;
|
||||
if (fractionDigits == null) {
|
||||
conversion = "'\$$name'";
|
||||
} else {
|
||||
conversion = "$name.toStringAsFixed($fractionDigits)";
|
||||
}
|
||||
if (padding!.isNotEmpty) {
|
||||
if (padding.startsWith("0")) {
|
||||
conversion += ".padLeft(${int.parse(padding)}, '0')";
|
||||
} else {
|
||||
conversion += ".padLeft(${int.parse(padding)})";
|
||||
}
|
||||
}
|
||||
return conversion;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "character":
|
||||
parameters.add("String character");
|
||||
conversions.add("if (character.runes.length != 1)"
|
||||
"throw \"Not a character '\${character}'\";");
|
||||
arguments.add("'$name': character");
|
||||
break;
|
||||
|
||||
case "unicode":
|
||||
// Write unicode value using at least four (but otherwise no more than
|
||||
// necessary) hex digits, using uppercase letters.
|
||||
// http://www.unicode.org/versions/Unicode10.0.0/appA.pdf
|
||||
parameters.add("int codePoint");
|
||||
conversions.add("String unicode = \"U+\${codePoint.toRadixString(16)"
|
||||
".toUpperCase().padLeft(4, '0')}\";");
|
||||
arguments.add("'$name': codePoint");
|
||||
break;
|
||||
|
||||
case "name":
|
||||
parameters.add("String name");
|
||||
conversions.add("if (name.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name");
|
||||
conversions.add("name = demangleMixinApplicationName(name);");
|
||||
break;
|
||||
|
||||
case "name2":
|
||||
parameters.add("String name2");
|
||||
conversions.add("if (name2.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name2");
|
||||
conversions.add("name2 = demangleMixinApplicationName(name2);");
|
||||
break;
|
||||
|
||||
case "name3":
|
||||
parameters.add("String name3");
|
||||
conversions.add("if (name3.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name3");
|
||||
conversions.add("name3 = demangleMixinApplicationName(name3);");
|
||||
break;
|
||||
|
||||
case "name4":
|
||||
parameters.add("String name4");
|
||||
conversions.add("if (name4.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name4");
|
||||
conversions.add("name4 = demangleMixinApplicationName(name4);");
|
||||
break;
|
||||
|
||||
case "nameOKEmpty":
|
||||
parameters.add("String nameOKEmpty");
|
||||
conversions.add("if (nameOKEmpty.isEmpty) "
|
||||
"nameOKEmpty = '(unnamed)';");
|
||||
arguments.add("'nameOKEmpty': nameOKEmpty");
|
||||
break;
|
||||
|
||||
case "names":
|
||||
parameters.add("List<String> _names");
|
||||
conversions.add("if (_names.isEmpty) throw 'No names provided';");
|
||||
arguments.add("'$name': _names");
|
||||
conversions.add("String names = itemizeNames(_names);");
|
||||
break;
|
||||
|
||||
case "lexeme":
|
||||
parameters.add("Token token");
|
||||
conversions.add("String lexeme = token.lexeme;");
|
||||
arguments.add("'$name': token");
|
||||
break;
|
||||
|
||||
case "lexeme2":
|
||||
parameters.add("Token token2");
|
||||
conversions.add("String lexeme2 = token2.lexeme;");
|
||||
arguments.add("'$name': token2");
|
||||
break;
|
||||
|
||||
case "string":
|
||||
parameters.add("String string");
|
||||
conversions.add("if (string.isEmpty) throw 'No string provided';");
|
||||
arguments.add("'$name': string");
|
||||
break;
|
||||
|
||||
case "string2":
|
||||
parameters.add("String string2");
|
||||
conversions.add("if (string2.isEmpty) throw 'No string provided';");
|
||||
arguments.add("'$name': string2");
|
||||
break;
|
||||
|
||||
case "string3":
|
||||
parameters.add("String string3");
|
||||
conversions.add("if (string3.isEmpty) throw 'No string provided';");
|
||||
arguments.add("'$name': string3");
|
||||
break;
|
||||
|
||||
case "stringOKEmpty":
|
||||
parameters.add("String stringOKEmpty");
|
||||
conversions.add("if (stringOKEmpty.isEmpty) "
|
||||
"stringOKEmpty = '(empty)';");
|
||||
arguments.add("'$name': stringOKEmpty");
|
||||
break;
|
||||
|
||||
case "type":
|
||||
case "type2":
|
||||
case "type3":
|
||||
case "type4":
|
||||
parameters.add("DartType _${name}");
|
||||
ensureLabeler();
|
||||
conversions
|
||||
.add("List<Object> ${name}Parts = labeler.labelType(_${name});");
|
||||
conversions2.add("String ${name} = ${name}Parts.join();");
|
||||
arguments.add("'${name}': _${name}");
|
||||
break;
|
||||
|
||||
case "uri":
|
||||
parameters.add("Uri uri_");
|
||||
conversions.add("String? uri = relativizeUri(uri_);");
|
||||
arguments.add("'$name': uri_");
|
||||
break;
|
||||
|
||||
case "uri2":
|
||||
parameters.add("Uri uri2_");
|
||||
conversions.add("String? uri2 = relativizeUri(uri2_);");
|
||||
arguments.add("'$name': uri2_");
|
||||
break;
|
||||
|
||||
case "uri3":
|
||||
parameters.add("Uri uri3_");
|
||||
conversions.add("String? uri3 = relativizeUri(uri3_);");
|
||||
arguments.add("'$name': uri3_");
|
||||
break;
|
||||
|
||||
case "count":
|
||||
parameters.add("int count");
|
||||
arguments.add("'$name': count");
|
||||
break;
|
||||
|
||||
case "count2":
|
||||
parameters.add("int count2");
|
||||
arguments.add("'$name': count2");
|
||||
break;
|
||||
|
||||
case "constant":
|
||||
parameters.add("Constant _constant");
|
||||
ensureLabeler();
|
||||
conversions.add(
|
||||
"List<Object> ${name}Parts = labeler.labelConstant(_${name});");
|
||||
conversions2.add("String ${name} = ${name}Parts.join();");
|
||||
arguments.add("'$name': _constant");
|
||||
break;
|
||||
|
||||
case "num1":
|
||||
parameters.add("num _num1");
|
||||
conversions.add("String num1 = ${format('_num1')};");
|
||||
arguments.add("'$name': _num1");
|
||||
break;
|
||||
|
||||
case "num2":
|
||||
parameters.add("num _num2");
|
||||
conversions.add("String num2 = ${format('_num2')};");
|
||||
arguments.add("'$name': _num2");
|
||||
break;
|
||||
|
||||
case "num3":
|
||||
parameters.add("num _num3");
|
||||
conversions.add("String num3 = ${format('_num3')};");
|
||||
arguments.add("'$name': _num3");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Unhandled placeholder in template: '$name'";
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLabeler) {
|
||||
parameters.add("bool isNonNullableByDefault");
|
||||
}
|
||||
|
||||
conversions.addAll(conversions2);
|
||||
|
||||
String interpolate(String text) {
|
||||
text = text
|
||||
.replaceAll(r"$", r"\$")
|
||||
.replaceAllMapped(placeholderPattern, (Match m) => "\${${m[1]}}");
|
||||
return "\"\"\"$text\"\"\"";
|
||||
}
|
||||
|
||||
List<String> codeArguments = <String>[];
|
||||
if (index != null) {
|
||||
codeArguments.add('index: $index');
|
||||
} else if (analyzerCode != null) {
|
||||
if (analyzerCode is String) {
|
||||
analyzerCode = <String>[analyzerCode];
|
||||
}
|
||||
List<Object?> codes = analyzerCode as List<Object?>;
|
||||
// If "index:" is defined, then "analyzerCode:" should not be generated
|
||||
// in the front end. See comment in messages.yaml
|
||||
codeArguments.add('analyzerCodes: <String>["${codes.join('", "')}"]');
|
||||
}
|
||||
if (severity != null) {
|
||||
String? severityEnumName = severityEnumNames[severity];
|
||||
if (severityEnumName == null) {
|
||||
throw "Unknown severity '$severity'";
|
||||
}
|
||||
codeArguments.add('severity: Severity.$severityEnumName');
|
||||
}
|
||||
|
||||
if (parameters.isEmpty && conversions.isEmpty && arguments.isEmpty) {
|
||||
codeArguments.add('problemMessage: r"""$problemMessage"""');
|
||||
if (correctionMessage != null) {
|
||||
codeArguments.add('correctionMessage: r"""$correctionMessage"""');
|
||||
}
|
||||
|
||||
return new Template("""
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> code$name = message$name;
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const MessageCode message$name =
|
||||
const MessageCode(\"$name\", ${codeArguments.join(', ')},);
|
||||
""", isShared: canBeShared);
|
||||
}
|
||||
|
||||
List<String> templateArguments = <String>[];
|
||||
templateArguments.add('\"$name\"');
|
||||
templateArguments.add('problemMessageTemplate: r"""$problemMessage"""');
|
||||
if (correctionMessage != null) {
|
||||
templateArguments
|
||||
.add('correctionMessageTemplate: r"""$correctionMessage"""');
|
||||
}
|
||||
|
||||
templateArguments.add("withArguments: _withArguments$name");
|
||||
|
||||
List<String> messageArguments = <String>[];
|
||||
String message = interpolate(problemMessage);
|
||||
if (hasLabeler) {
|
||||
message += " + labeler.originMessages";
|
||||
}
|
||||
messageArguments.add("problemMessage: ${message}");
|
||||
if (correctionMessage != null) {
|
||||
messageArguments
|
||||
.add("correctionMessage: ${interpolate(correctionMessage)}");
|
||||
}
|
||||
messageArguments.add("arguments: { ${arguments.join(', ')}, }");
|
||||
|
||||
if (codeArguments.isNotEmpty) {
|
||||
codeArguments.add("");
|
||||
}
|
||||
|
||||
return new Template("""
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(${parameters.join(', ')})> template$name =
|
||||
const Template<Message Function(${parameters.join(', ')})>(
|
||||
${templateArguments.join(', ')},);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(${parameters.join(', ')})> code$name =
|
||||
const Code<Message Function(${parameters.join(', ')})>(
|
||||
\"$name\", ${codeArguments.join(', ')});
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArguments$name(${parameters.join(', ')}) {
|
||||
${conversions.join('\n ')}
|
||||
return new Message(
|
||||
code$name,
|
||||
${messageArguments.join(', ')},);
|
||||
}
|
||||
""", isShared: canBeShared);
|
||||
return generateMessagesFilesRaw(
|
||||
repoDir, (s) => new DartFormatter().format(s));
|
||||
}
|
||||
|
|
39
pkg/front_end/tool/_fasta/generate_messages_failsafe.dart
Normal file
39
pkg/front_end/tool/_fasta/generate_messages_failsafe.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2024, 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 '../../test/utils/io_utils.dart' show computeRepoDirUri;
|
||||
|
||||
import 'generate_messages_lib.dart';
|
||||
|
||||
void main(List<String> arguments) {
|
||||
print("Running the fail-safe version.");
|
||||
final Uri repoDir = computeRepoDirUri();
|
||||
Messages message = generateMessagesFilesRaw(repoDir, (s) => s);
|
||||
if (message.sharedMessages.trim().isEmpty ||
|
||||
message.cfeMessages.trim().isEmpty) {
|
||||
print("Bailing because of errors: "
|
||||
"Refusing to overwrite with empty file!");
|
||||
} else {
|
||||
new File.fromUri(computeSharedGeneratedFile(repoDir))
|
||||
.writeAsStringSync(message.sharedMessages, flush: true);
|
||||
new File.fromUri(computeCfeGeneratedFile(repoDir))
|
||||
.writeAsStringSync(message.cfeMessages, flush: true);
|
||||
}
|
||||
|
||||
if (exitCode != 0) {
|
||||
print("Something went wrong.");
|
||||
return;
|
||||
}
|
||||
|
||||
print("Now executing the non-failsafe version.");
|
||||
|
||||
ProcessResult run = Process.runSync(Platform.resolvedExecutable,
|
||||
[Platform.script.resolve("generate_messages.dart").toFilePath()]);
|
||||
stderr.writeln(run.stderr);
|
||||
stdout.writeln(run.stdout);
|
||||
if (run.exitCode != 0) exitCode = 1;
|
||||
print("Done with exit code $exitCode");
|
||||
}
|
459
pkg/front_end/tool/_fasta/generate_messages_lib.dart
Normal file
459
pkg/front_end/tool/_fasta/generate_messages_lib.dart
Normal file
|
@ -0,0 +1,459 @@
|
|||
// Copyright (c) 2024, 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' show File, exitCode;
|
||||
|
||||
import "package:_fe_analyzer_shared/src/messages/severity.dart"
|
||||
show severityEnumNames;
|
||||
|
||||
import 'package:yaml/yaml.dart' show loadYaml;
|
||||
|
||||
Uri computeSharedGeneratedFile(Uri repoDir) {
|
||||
return repoDir
|
||||
.resolve("pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart");
|
||||
}
|
||||
|
||||
Uri computeCfeGeneratedFile(Uri repoDir) {
|
||||
return repoDir
|
||||
.resolve("pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart");
|
||||
}
|
||||
|
||||
class Messages {
|
||||
final String sharedMessages;
|
||||
final String cfeMessages;
|
||||
|
||||
Messages(this.sharedMessages, this.cfeMessages);
|
||||
}
|
||||
|
||||
Messages generateMessagesFilesRaw(
|
||||
Uri repoDir, String Function(String) formatter) {
|
||||
Uri messagesFile = repoDir.resolve("pkg/front_end/messages.yaml");
|
||||
Map<dynamic, dynamic> yaml =
|
||||
loadYaml(new File.fromUri(messagesFile).readAsStringSync());
|
||||
StringBuffer sharedMessages = new StringBuffer();
|
||||
StringBuffer cfeMessages = new StringBuffer();
|
||||
|
||||
const String preamble1 = """
|
||||
// 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.
|
||||
|
||||
""";
|
||||
|
||||
const String preamble2 = """
|
||||
|
||||
// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
|
||||
//
|
||||
// Instead modify 'pkg/front_end/messages.yaml' and run
|
||||
// 'pkg/front_end/tool/fasta generate-messages' to update.
|
||||
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
""";
|
||||
|
||||
sharedMessages.writeln(preamble1);
|
||||
sharedMessages.writeln(preamble2);
|
||||
sharedMessages.writeln("""
|
||||
part of _fe_analyzer_shared.messages.codes;
|
||||
""");
|
||||
|
||||
cfeMessages.writeln(preamble1);
|
||||
cfeMessages.writeln("""
|
||||
|
||||
""");
|
||||
cfeMessages.writeln(preamble2);
|
||||
cfeMessages.writeln("""
|
||||
part of fasta.codes;
|
||||
""");
|
||||
|
||||
bool hasError = false;
|
||||
int largestIndex = 0;
|
||||
final indexNameMap = new Map<int, String>();
|
||||
|
||||
List<String> keys = yaml.keys.cast<String>().toList()..sort();
|
||||
for (String name in keys) {
|
||||
var description = yaml[name];
|
||||
while (description is String) {
|
||||
description = yaml[description];
|
||||
}
|
||||
Map<dynamic, dynamic>? map = description;
|
||||
if (map == null) {
|
||||
throw "No 'problemMessage:' in key $name.";
|
||||
}
|
||||
var index = map['index'];
|
||||
if (index != null) {
|
||||
if (index is! int || index < 1) {
|
||||
print('Error: Expected positive int for "index:" field in $name,'
|
||||
' but found $index');
|
||||
hasError = true;
|
||||
index = -1;
|
||||
// Continue looking for other problems.
|
||||
} else {
|
||||
String? otherName = indexNameMap[index];
|
||||
if (otherName != null) {
|
||||
print('Error: The "index:" field must be unique, '
|
||||
'but is the same for $otherName and $name');
|
||||
hasError = true;
|
||||
// Continue looking for other problems.
|
||||
} else {
|
||||
indexNameMap[index] = name;
|
||||
if (largestIndex < index) {
|
||||
largestIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Template template = compileTemplate(name, index, map['problemMessage'],
|
||||
map['correctionMessage'], map['analyzerCode'], map['severity']);
|
||||
if (template.isShared) {
|
||||
sharedMessages.writeln(template.text);
|
||||
} else {
|
||||
cfeMessages.writeln(template.text);
|
||||
}
|
||||
}
|
||||
if (largestIndex > indexNameMap.length) {
|
||||
print('Error: The "index:" field values should be unique, consecutive'
|
||||
' whole numbers starting with 1.');
|
||||
hasError = true;
|
||||
// Fall through to print more information.
|
||||
}
|
||||
if (hasError) {
|
||||
exitCode = 1;
|
||||
print('The largest index is $largestIndex');
|
||||
final sortedIndices = indexNameMap.keys.toList()..sort();
|
||||
int nextAvailableIndex = largestIndex + 1;
|
||||
for (int index = 1; index <= sortedIndices.length; ++index) {
|
||||
if (sortedIndices[index - 1] != index) {
|
||||
nextAvailableIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
print('The next available index is ${nextAvailableIndex}');
|
||||
return new Messages('', '');
|
||||
}
|
||||
|
||||
return new Messages(formatter("$sharedMessages"), formatter("$cfeMessages"));
|
||||
}
|
||||
|
||||
final RegExp placeholderPattern =
|
||||
new RegExp("#\([-a-zA-Z0-9_]+\)(?:%\([0-9]*\)\.\([0-9]+\))?");
|
||||
|
||||
class Template {
|
||||
final String text;
|
||||
final isShared;
|
||||
|
||||
Template(this.text, {this.isShared}) : assert(isShared != null);
|
||||
}
|
||||
|
||||
Template compileTemplate(String name, int? index, String? problemMessage,
|
||||
String? correctionMessage, Object? analyzerCode, String? severity) {
|
||||
if (problemMessage == null) {
|
||||
print('Error: missing problemMessage for message: $name');
|
||||
exitCode = 1;
|
||||
return new Template('', isShared: true);
|
||||
}
|
||||
// Remove trailing whitespace. This is necessary for templates defined with
|
||||
// `|` (verbatim) as they always contain a trailing newline that we don't
|
||||
// want.
|
||||
problemMessage = problemMessage.trimRight();
|
||||
var parameters = new Set<String>();
|
||||
var conversions = new Set<String>();
|
||||
var conversions2 = new Set<String>();
|
||||
var arguments = new Set<String>();
|
||||
bool hasLabeler = false;
|
||||
bool canBeShared = true;
|
||||
void ensureLabeler() {
|
||||
if (hasLabeler) return;
|
||||
conversions
|
||||
.add("TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);");
|
||||
hasLabeler = true;
|
||||
canBeShared = false;
|
||||
}
|
||||
|
||||
for (Match match in placeholderPattern
|
||||
.allMatches("$problemMessage\n${correctionMessage ?? ''}")) {
|
||||
String name = match[1]!;
|
||||
String? padding = match[2];
|
||||
String? fractionDigits = match[3];
|
||||
|
||||
String format(String name) {
|
||||
String conversion;
|
||||
if (fractionDigits == null) {
|
||||
conversion = "'\$$name'";
|
||||
} else {
|
||||
conversion = "$name.toStringAsFixed($fractionDigits)";
|
||||
}
|
||||
if (padding!.isNotEmpty) {
|
||||
if (padding.startsWith("0")) {
|
||||
conversion += ".padLeft(${int.parse(padding)}, '0')";
|
||||
} else {
|
||||
conversion += ".padLeft(${int.parse(padding)})";
|
||||
}
|
||||
}
|
||||
return conversion;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "character":
|
||||
parameters.add("String character");
|
||||
conversions.add("if (character.runes.length != 1)"
|
||||
"throw \"Not a character '\${character}'\";");
|
||||
arguments.add("'$name': character");
|
||||
break;
|
||||
|
||||
case "unicode":
|
||||
// Write unicode value using at least four (but otherwise no more than
|
||||
// necessary) hex digits, using uppercase letters.
|
||||
// http://www.unicode.org/versions/Unicode10.0.0/appA.pdf
|
||||
parameters.add("int codePoint");
|
||||
conversions.add("String unicode = \"U+\${codePoint.toRadixString(16)"
|
||||
".toUpperCase().padLeft(4, '0')}\";");
|
||||
arguments.add("'$name': codePoint");
|
||||
break;
|
||||
|
||||
case "name":
|
||||
parameters.add("String name");
|
||||
conversions.add("if (name.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name");
|
||||
conversions.add("name = demangleMixinApplicationName(name);");
|
||||
break;
|
||||
|
||||
case "name2":
|
||||
parameters.add("String name2");
|
||||
conversions.add("if (name2.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name2");
|
||||
conversions.add("name2 = demangleMixinApplicationName(name2);");
|
||||
break;
|
||||
|
||||
case "name3":
|
||||
parameters.add("String name3");
|
||||
conversions.add("if (name3.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name3");
|
||||
conversions.add("name3 = demangleMixinApplicationName(name3);");
|
||||
break;
|
||||
|
||||
case "name4":
|
||||
parameters.add("String name4");
|
||||
conversions.add("if (name4.isEmpty) throw 'No name provided';");
|
||||
arguments.add("'$name': name4");
|
||||
conversions.add("name4 = demangleMixinApplicationName(name4);");
|
||||
break;
|
||||
|
||||
case "nameOKEmpty":
|
||||
parameters.add("String nameOKEmpty");
|
||||
conversions.add("if (nameOKEmpty.isEmpty) "
|
||||
"nameOKEmpty = '(unnamed)';");
|
||||
arguments.add("'nameOKEmpty': nameOKEmpty");
|
||||
break;
|
||||
|
||||
case "names":
|
||||
parameters.add("List<String> _names");
|
||||
conversions.add("if (_names.isEmpty) throw 'No names provided';");
|
||||
arguments.add("'$name': _names");
|
||||
conversions.add("String names = itemizeNames(_names);");
|
||||
break;
|
||||
|
||||
case "lexeme":
|
||||
parameters.add("Token token");
|
||||
conversions.add("String lexeme = token.lexeme;");
|
||||
arguments.add("'$name': token");
|
||||
break;
|
||||
|
||||
case "lexeme2":
|
||||
parameters.add("Token token2");
|
||||
conversions.add("String lexeme2 = token2.lexeme;");
|
||||
arguments.add("'$name': token2");
|
||||
break;
|
||||
|
||||
case "string":
|
||||
parameters.add("String string");
|
||||
conversions.add("if (string.isEmpty) throw 'No string provided';");
|
||||
arguments.add("'$name': string");
|
||||
break;
|
||||
|
||||
case "string2":
|
||||
parameters.add("String string2");
|
||||
conversions.add("if (string2.isEmpty) throw 'No string provided';");
|
||||
arguments.add("'$name': string2");
|
||||
break;
|
||||
|
||||
case "string3":
|
||||
parameters.add("String string3");
|
||||
conversions.add("if (string3.isEmpty) throw 'No string provided';");
|
||||
arguments.add("'$name': string3");
|
||||
break;
|
||||
|
||||
case "stringOKEmpty":
|
||||
parameters.add("String stringOKEmpty");
|
||||
conversions.add("if (stringOKEmpty.isEmpty) "
|
||||
"stringOKEmpty = '(empty)';");
|
||||
arguments.add("'$name': stringOKEmpty");
|
||||
break;
|
||||
|
||||
case "type":
|
||||
case "type2":
|
||||
case "type3":
|
||||
case "type4":
|
||||
parameters.add("DartType _${name}");
|
||||
ensureLabeler();
|
||||
conversions
|
||||
.add("List<Object> ${name}Parts = labeler.labelType(_${name});");
|
||||
conversions2.add("String ${name} = ${name}Parts.join();");
|
||||
arguments.add("'${name}': _${name}");
|
||||
break;
|
||||
|
||||
case "uri":
|
||||
parameters.add("Uri uri_");
|
||||
conversions.add("String? uri = relativizeUri(uri_);");
|
||||
arguments.add("'$name': uri_");
|
||||
break;
|
||||
|
||||
case "uri2":
|
||||
parameters.add("Uri uri2_");
|
||||
conversions.add("String? uri2 = relativizeUri(uri2_);");
|
||||
arguments.add("'$name': uri2_");
|
||||
break;
|
||||
|
||||
case "uri3":
|
||||
parameters.add("Uri uri3_");
|
||||
conversions.add("String? uri3 = relativizeUri(uri3_);");
|
||||
arguments.add("'$name': uri3_");
|
||||
break;
|
||||
|
||||
case "count":
|
||||
parameters.add("int count");
|
||||
arguments.add("'$name': count");
|
||||
break;
|
||||
|
||||
case "count2":
|
||||
parameters.add("int count2");
|
||||
arguments.add("'$name': count2");
|
||||
break;
|
||||
|
||||
case "constant":
|
||||
parameters.add("Constant _constant");
|
||||
ensureLabeler();
|
||||
conversions.add(
|
||||
"List<Object> ${name}Parts = labeler.labelConstant(_${name});");
|
||||
conversions2.add("String ${name} = ${name}Parts.join();");
|
||||
arguments.add("'$name': _constant");
|
||||
break;
|
||||
|
||||
case "num1":
|
||||
parameters.add("num _num1");
|
||||
conversions.add("String num1 = ${format('_num1')};");
|
||||
arguments.add("'$name': _num1");
|
||||
break;
|
||||
|
||||
case "num2":
|
||||
parameters.add("num _num2");
|
||||
conversions.add("String num2 = ${format('_num2')};");
|
||||
arguments.add("'$name': _num2");
|
||||
break;
|
||||
|
||||
case "num3":
|
||||
parameters.add("num _num3");
|
||||
conversions.add("String num3 = ${format('_num3')};");
|
||||
arguments.add("'$name': _num3");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Unhandled placeholder in template: '$name'";
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLabeler) {
|
||||
parameters.add("bool isNonNullableByDefault");
|
||||
}
|
||||
|
||||
conversions.addAll(conversions2);
|
||||
|
||||
String interpolate(String text) {
|
||||
text = text
|
||||
.replaceAll(r"$", r"\$")
|
||||
.replaceAllMapped(placeholderPattern, (Match m) => "\${${m[1]}}");
|
||||
return "\"\"\"$text\"\"\"";
|
||||
}
|
||||
|
||||
List<String> codeArguments = <String>[];
|
||||
if (index != null) {
|
||||
codeArguments.add('index: $index');
|
||||
} else if (analyzerCode != null) {
|
||||
if (analyzerCode is String) {
|
||||
analyzerCode = <String>[analyzerCode];
|
||||
}
|
||||
List<Object?> codes = analyzerCode as List<Object?>;
|
||||
// If "index:" is defined, then "analyzerCode:" should not be generated
|
||||
// in the front end. See comment in messages.yaml
|
||||
codeArguments.add('analyzerCodes: <String>["${codes.join('", "')}"]');
|
||||
}
|
||||
if (severity != null) {
|
||||
String? severityEnumName = severityEnumNames[severity];
|
||||
if (severityEnumName == null) {
|
||||
throw "Unknown severity '$severity'";
|
||||
}
|
||||
codeArguments.add('severity: Severity.$severityEnumName');
|
||||
}
|
||||
|
||||
if (parameters.isEmpty && conversions.isEmpty && arguments.isEmpty) {
|
||||
codeArguments.add('problemMessage: r"""$problemMessage"""');
|
||||
if (correctionMessage != null) {
|
||||
codeArguments.add('correctionMessage: r"""$correctionMessage"""');
|
||||
}
|
||||
|
||||
return new Template("""
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Null> code$name = message$name;
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const MessageCode message$name =
|
||||
const MessageCode(\"$name\", ${codeArguments.join(', ')},);
|
||||
""", isShared: canBeShared);
|
||||
}
|
||||
|
||||
List<String> templateArguments = <String>[];
|
||||
templateArguments.add('\"$name\"');
|
||||
templateArguments.add('problemMessageTemplate: r"""$problemMessage"""');
|
||||
if (correctionMessage != null) {
|
||||
templateArguments
|
||||
.add('correctionMessageTemplate: r"""$correctionMessage"""');
|
||||
}
|
||||
|
||||
templateArguments.add("withArguments: _withArguments$name");
|
||||
|
||||
List<String> messageArguments = <String>[];
|
||||
String message = interpolate(problemMessage);
|
||||
if (hasLabeler) {
|
||||
message += " + labeler.originMessages";
|
||||
}
|
||||
messageArguments.add("problemMessage: ${message}");
|
||||
if (correctionMessage != null) {
|
||||
messageArguments
|
||||
.add("correctionMessage: ${interpolate(correctionMessage)}");
|
||||
}
|
||||
messageArguments.add("arguments: { ${arguments.join(', ')}, }");
|
||||
|
||||
if (codeArguments.isNotEmpty) {
|
||||
codeArguments.add("");
|
||||
}
|
||||
|
||||
return new Template("""
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Template<Message Function(${parameters.join(', ')})> template$name =
|
||||
const Template<Message Function(${parameters.join(', ')})>(
|
||||
${templateArguments.join(', ')},);
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
const Code<Message Function(${parameters.join(', ')})> code$name =
|
||||
const Code<Message Function(${parameters.join(', ')})>(
|
||||
\"$name\", ${codeArguments.join(', ')});
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
|
||||
Message _withArguments$name(${parameters.join(', ')}) {
|
||||
${conversions.join('\n ')}
|
||||
return new Message(
|
||||
code$name,
|
||||
${messageArguments.join(', ')},);
|
||||
}
|
||||
""", isShared: canBeShared);
|
||||
}
|
Loading…
Reference in a new issue