[front_end] Use shared logic for interpreting messages.yaml file.
Reworks the logic in `pkg/front_end/tool/generate_messages_lib.dart`
to take advantage of shared functionality in
`pkg/analyzer_utilities/lib/messages.dart`. This reduces code
duplication between the CFE and analyzer, and should help pave the way
for sharing more diagnostic message generation logic over time.
The following declarations are moved into
`pkg/analyzer_utilities/lib/messages.dart` to avoid introducing a
circular dependency between `pkg/analyzer_utilities` and
`pkg/front_end`:
- `severityEnumNames`
- `_templateParameterNameToType`
- `Conversion`, `LabelerConversion`, `NumericConversion`, and
`SimpleConversion`
- `ParsedPlaceholder`
Also, the enum `_TemplateParameterType` is removed in favor of the
shared enum `ErrorCodeParameterType`. To accommodate parameter types
that haven't yet been fully harmonized between the analyzer and CFE
representations, `ErrorCodeParameterType` is now capable of
representing CFE-only and analyzer-only types. In the long run I
expect to update the analyzer so that it fully supports all diagnostic
parameter types.
Change-Id: I6a6a6964928631a6f73a354f9f2d7275197f84b2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/448249
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart b/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
index b3547e9..8d97a6e 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
@@ -6,15 +6,6 @@
enum CfeSeverity { context, error, ignored, internalProblem, warning, info }
-const Map<String, String> severityEnumNames = const <String, String>{
- 'CONTEXT': 'context',
- 'ERROR': 'error',
- 'IGNORED': 'ignored',
- 'INTERNAL_PROBLEM': 'internalProblem',
- 'WARNING': 'warning',
- 'INFO': 'info',
-};
-
const Map<String, CfeSeverity> severityEnumValues = const <String, CfeSeverity>{
'CONTEXT': CfeSeverity.context,
'ERROR': CfeSeverity.error,
diff --git a/pkg/analyzer_utilities/lib/messages.dart b/pkg/analyzer_utilities/lib/messages.dart
index 3a4a72a..159f5aa 100644
--- a/pkg/analyzer_utilities/lib/messages.dart
+++ b/pkg/analyzer_utilities/lib/messages.dart
@@ -11,6 +11,52 @@
import 'package:path/path.dart';
import 'package:yaml/yaml.dart' show loadYaml;
+const Map<String, String> severityEnumNames = <String, String>{
+ 'CONTEXT': 'context',
+ 'ERROR': 'error',
+ 'IGNORED': 'ignored',
+ 'INTERNAL_PROBLEM': 'internalProblem',
+ 'WARNING': 'warning',
+ 'INFO': 'info',
+};
+
+/// Map assigning each possible template parameter a [ErrorCodeParameterType].
+///
+// TODO(paulberry): Change the format of `messages.yaml` so that the template
+// parameters, and their types, are stated explicitly, as they are in the
+// analyzer's `messages.yaml` file. Then this constant will not be needed.
+const _templateParameterNameToType = {
+ 'character': ErrorCodeParameterType.character,
+ 'unicode': ErrorCodeParameterType.unicode,
+ 'name': ErrorCodeParameterType.name,
+ 'name2': ErrorCodeParameterType.name,
+ 'name3': ErrorCodeParameterType.name,
+ 'name4': ErrorCodeParameterType.name,
+ 'nameOKEmpty': ErrorCodeParameterType.nameOKEmpty,
+ 'names': ErrorCodeParameterType.names,
+ 'lexeme': ErrorCodeParameterType.token,
+ 'lexeme2': ErrorCodeParameterType.token,
+ 'string': ErrorCodeParameterType.string,
+ 'string2': ErrorCodeParameterType.string,
+ 'string3': ErrorCodeParameterType.string,
+ 'stringOKEmpty': ErrorCodeParameterType.stringOKEmpty,
+ 'type': ErrorCodeParameterType.type,
+ 'type2': ErrorCodeParameterType.type,
+ 'type3': ErrorCodeParameterType.type,
+ 'type4': ErrorCodeParameterType.type,
+ 'uri': ErrorCodeParameterType.uri,
+ 'uri2': ErrorCodeParameterType.uri,
+ 'uri3': ErrorCodeParameterType.uri,
+ 'count': ErrorCodeParameterType.int,
+ 'count2': ErrorCodeParameterType.int,
+ 'count3': ErrorCodeParameterType.int,
+ 'count4': ErrorCodeParameterType.int,
+ 'constant': ErrorCodeParameterType.constant,
+ 'num1': ErrorCodeParameterType.num,
+ 'num2': ErrorCodeParameterType.num,
+ 'num3': ErrorCodeParameterType.num,
+};
+
/// Decoded messages from the front end's `messages.yaml` file.
final Map<String, FrontEndErrorCodeInfo> frontEndMessages =
_loadFrontEndMessages();
@@ -28,8 +74,6 @@
final RegExp oldPlaceholderPattern = RegExp(r'\{\d+\}');
/// Pattern for placeholders in error message strings.
-// TODO(paulberry): share this regexp (and the code for interpreting
-// it) between the CFE and analyzer.
final RegExp placeholderPattern = RegExp(
'#([-a-zA-Z0-9_]+)(?:%([0-9]*).([0-9]+))?',
);
@@ -121,6 +165,19 @@
return lines;
}
+/// Information about how to convert the CFE's internal representation of a
+/// template parameter to a string.
+///
+/// Instances of this class should implement [==] and [hashCode] so that they
+/// can be used as keys in a [Map].
+sealed class Conversion {
+ /// Returns Dart code that applies the conversion to a template parameter
+ /// having the given [name] and [type].
+ ///
+ /// If no conversion is needed, returns `null`.
+ String? toCode({required String name, required ErrorCodeParameterType type});
+}
+
/// Information about a code generated class derived from `ErrorCode`.
class ErrorClassInfo {
/// The generated file containing this class.
@@ -357,7 +414,8 @@
'Error code declares parameters using a `parameters` entry, but '
"doesn't use them",
);
- } else if (parameters == null) {
+ } else if (parameters == null ||
+ parameters.any((p) => !p.type.isSupportedByAnalyzer)) {
// Do not generate literate API yet.
className = errorClassInfo.name;
} else if (parameters.isNotEmpty) {
@@ -564,12 +622,73 @@
/// In-memory representation of the type of a single diagnostic code's
/// parameter.
enum ErrorCodeParameterType {
+ character(
+ messagesYamlName: 'Character',
+ cfeName: 'String',
+ cfeConversion: SimpleConversion('validateCharacter'),
+ ),
+ constant(
+ messagesYamlName: 'Constant',
+ cfeName: 'Constant',
+ cfeConversion: LabelerConversion('labelConstant'),
+ ),
element(messagesYamlName: 'Element', analyzerName: 'Element'),
- int(messagesYamlName: 'int', analyzerName: 'int'),
+ int(messagesYamlName: 'int', analyzerName: 'int', cfeName: 'int'),
+ name(
+ messagesYamlName: 'Name',
+ cfeName: 'String',
+ cfeConversion: SimpleConversion('validateAndDemangleName'),
+ ),
+ nameOKEmpty(
+ messagesYamlName: 'NameOKEmpty',
+ cfeName: 'String',
+ cfeConversion: SimpleConversion('nameOrUnnamed'),
+ ),
+ names(
+ messagesYamlName: 'Names',
+ cfeName: 'List<String>',
+ cfeConversion: SimpleConversion('validateAndItemizeNames'),
+ ),
+ num(
+ messagesYamlName: 'num',
+ cfeName: 'num',
+ cfeConversion: SimpleConversion('formatNumber'),
+ ),
object(messagesYamlName: 'Object', analyzerName: 'Object'),
- string(messagesYamlName: 'String', analyzerName: 'String'),
- type(messagesYamlName: 'Type', analyzerName: 'DartType'),
- uri(messagesYamlName: 'Uri', analyzerName: 'Uri');
+ string(
+ messagesYamlName: 'String',
+ analyzerName: 'String',
+ cfeName: 'String',
+ cfeConversion: SimpleConversion('validateString'),
+ ),
+ stringOKEmpty(
+ messagesYamlName: 'StringOKEmpty',
+ analyzerName: 'String',
+ cfeName: 'String',
+ cfeConversion: SimpleConversion('stringOrEmpty'),
+ ),
+ token(
+ messagesYamlName: 'Token',
+ cfeName: 'Token',
+ cfeConversion: SimpleConversion('tokenToLexeme'),
+ ),
+ type(
+ messagesYamlName: 'Type',
+ analyzerName: 'DartType',
+ cfeName: 'DartType',
+ cfeConversion: LabelerConversion('labelType'),
+ ),
+ unicode(
+ messagesYamlName: 'Unicode',
+ cfeName: 'int',
+ cfeConversion: SimpleConversion('codePointToUnicode'),
+ ),
+ uri(
+ messagesYamlName: 'Uri',
+ analyzerName: 'Uri',
+ cfeName: 'Uri',
+ cfeConversion: SimpleConversion('relativizeUri'),
+ );
/// Map from [messagesYamlName] to the enum constant.
///
@@ -581,18 +700,47 @@
/// Name of this type as it appears in `messages.yaml`.
final String messagesYamlName;
- /// Name of this type as it appears in Dart source code.
- final String analyzerName;
+ /// Name of this type as it appears in analyzer source code.
+ ///
+ /// If `null`, diagnostic messages using parameters of this type are not yet
+ /// supported by the analyzer (see [isSupportedByAnalyzer])
+ final String? _analyzerName;
+
+ /// Name of this type as it appears in CFE source code.
+ ///
+ /// If `null`, diagnostic messages using parameters of this type are not
+ /// supported by the CFE.
+ final String? cfeName;
+
+ /// How to convert the CFE's internal representation of a template parameter
+ /// to a string.
+ ///
+ /// This field will be `null` if either:
+ /// - Diagnostic messages using parameters of this type are not supported by
+ /// the CFE (and hence no CFE conversion is needed), or
+ /// - No CFE conversion is needed because the type's `toString` method is
+ /// sufficient.
+ final Conversion? cfeConversion;
const ErrorCodeParameterType({
required this.messagesYamlName,
- required this.analyzerName,
- });
+ String? analyzerName,
+ this.cfeName,
+ this.cfeConversion,
+ }) : _analyzerName = analyzerName;
/// Decodes a type name from `messages.yaml` into an [ErrorCodeParameterName].
factory ErrorCodeParameterType.fromMessagesYamlName(String name) =>
_messagesYamlNameToValue[name] ??
(throw StateError('Unknown type name: $name'));
+
+ String get analyzerName =>
+ _analyzerName ??
+ (throw 'No analyzer support for type ${json.encode(messagesYamlName)}');
+
+ /// Whether giatnostic messages using parameters of this type are supported by
+ /// the analyzer.
+ bool get isSupportedByAnalyzer => _analyzerName != null;
}
/// In-memory representation of error code information obtained from the front
@@ -605,10 +753,19 @@
/// The index of the error in the analyzer's `fastaAnalyzerErrorCodes` table.
final int? index;
- FrontEndErrorCodeInfo.fromYaml(super.yaml)
+ /// The name of the [CfeSeverity] constant describing this error code's CFE
+ /// severity.
+ final String? cfeSeverity;
+
+ FrontEndErrorCodeInfo.fromYaml(Map<Object?, Object?> yaml)
: analyzerCode = _decodeAnalyzerCode(yaml['analyzerCode']),
- index = yaml['index'] as int?,
- super.fromYaml();
+ index = _decodeIndex(yaml['index']),
+ cfeSeverity = _decodeSeverity(yaml['severity']),
+ super.fromYaml(yaml) {
+ if (yaml['problemMessage'] == null) {
+ throw 'Missing problemMessage';
+ }
+ }
@override
Map<Object?, Object?> toYaml() => {
@@ -630,6 +787,30 @@
}
}
+ static int? _decodeIndex(Object? value) {
+ switch (value) {
+ case null:
+ return null;
+ case int():
+ if (value >= 1) {
+ return value;
+ }
+ }
+ throw 'Expected positive int for "index:", but found $value';
+ }
+
+ static String? _decodeSeverity(Object? yamlEntry) {
+ switch (yamlEntry) {
+ case null:
+ return null;
+ case String():
+ return severityEnumNames[yamlEntry] ??
+ (throw "Unknown severity '$yamlEntry'");
+ default:
+ throw 'Bad severity type: ${yamlEntry.runtimeType}';
+ }
+ }
+
static Object _encodeAnalyzerCode(List<String> analyzerCode) {
if (analyzerCode.length == 1) {
return analyzerCode.single;
@@ -661,3 +842,167 @@
this.shouldIgnorePreferSingleQuotes = false,
});
}
+
+/// A [Conversion] that makes use of the [TypeLabeler] class.
+class LabelerConversion implements Conversion {
+ /// The name of the [TypeLabeler] method to call.
+ final String methodName;
+
+ const LabelerConversion(this.methodName);
+
+ @override
+ int get hashCode => Object.hash(runtimeType, methodName.hashCode);
+
+ @override
+ bool operator ==(Object other) =>
+ other is LabelerConversion && other.methodName == methodName;
+
+ @override
+ String toCode({required String name, required ErrorCodeParameterType type}) =>
+ 'labeler.$methodName($name)';
+}
+
+/// A [Conversion] that acts on [num], applying formatting parameters specified
+/// in the template.
+class NumericConversion implements Conversion {
+ /// If non-null, the number of digits to show after the decimal point.
+ final int? fractionDigits;
+
+ /// The minimum number of characters of output to be generated.
+ ///
+ /// If the number does not require this many characters to display, extra
+ /// padding characters are inserted to the left.
+ final int padWidth;
+
+ /// If `true`, '0' is used for padding (see [padWidth]); otherwise ' ' is
+ /// used.
+ final bool padWithZeros;
+
+ NumericConversion({
+ required this.fractionDigits,
+ required this.padWidth,
+ required this.padWithZeros,
+ });
+
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ fractionDigits.hashCode,
+ padWidth.hashCode,
+ padWithZeros.hashCode,
+ );
+
+ @override
+ bool operator ==(Object other) =>
+ other is NumericConversion &&
+ other.fractionDigits == fractionDigits &&
+ other.padWidth == padWidth &&
+ other.padWithZeros == padWithZeros;
+
+ @override
+ String? toCode({required String name, required ErrorCodeParameterType type}) {
+ if (type != ErrorCodeParameterType.num) {
+ throw 'format suffix may only be applied to parameters of type num';
+ }
+ return 'conversions.formatNumber($name, fractionDigits: $fractionDigits, '
+ 'padWidth: $padWidth, padWithZeros: $padWithZeros)';
+ }
+
+ /// Creates a [NumericConversion] from the given regular expression [match].
+ ///
+ /// [match] should be the result of matching [placeholderPattern] to the
+ /// template string.
+ ///
+ /// Returns `null` if no special numeric conversion is needed.
+ static NumericConversion? from(Match match) {
+ String? padding = match[2];
+ String? fractionDigitsStr = match[3];
+
+ int? fractionDigits = fractionDigitsStr == null
+ ? null
+ : int.parse(fractionDigitsStr);
+ if (padding != null && padding.isNotEmpty) {
+ return NumericConversion(
+ fractionDigits: fractionDigits,
+ padWidth: int.parse(padding),
+ padWithZeros: padding.startsWith('0'),
+ );
+ } else if (fractionDigits != null) {
+ return NumericConversion(
+ fractionDigits: fractionDigits,
+ padWidth: 0,
+ padWithZeros: false,
+ );
+ } else {
+ return null;
+ }
+ }
+}
+
+/// The result of parsing a [placeholderPattern] match in a template string.
+class ParsedPlaceholder {
+ /// The name of the template parameter.
+ ///
+ /// This is the identifier that immediately follows the `#`.
+ final String name;
+
+ /// The type of the corresponding template parameter.
+ final ErrorCodeParameterType templateParameterType;
+
+ /// The conversion that should be applied to the template parameter.
+ final Conversion? conversion;
+
+ /// Builds a [ParsedPlaceholder] from the given [match] of
+ /// [placeholderPattern].
+ factory ParsedPlaceholder.fromMatch(Match match) {
+ String name = match[1]!;
+
+ var templateParameterType = _templateParameterNameToType[name];
+ if (templateParameterType == null) {
+ throw "Unhandled placeholder in template: '$name'";
+ }
+
+ return ParsedPlaceholder._(
+ name: name,
+ templateParameterType: templateParameterType,
+ conversion:
+ NumericConversion.from(match) ?? templateParameterType.cfeConversion,
+ );
+ }
+
+ ParsedPlaceholder._({
+ required this.name,
+ required this.templateParameterType,
+ required this.conversion,
+ });
+
+ @override
+ int get hashCode => Object.hash(name, templateParameterType, conversion);
+
+ @override
+ bool operator ==(Object other) =>
+ other is ParsedPlaceholder &&
+ other.name == name &&
+ other.templateParameterType == templateParameterType &&
+ other.conversion == conversion;
+}
+
+/// A [Conversion] that invokes a top level function via the `conversions`
+/// import prefix.
+class SimpleConversion implements Conversion {
+ /// The name of the function to be invoked.
+ final String functionName;
+
+ const SimpleConversion(this.functionName);
+
+ @override
+ int get hashCode => Object.hash(runtimeType, functionName.hashCode);
+
+ @override
+ bool operator ==(Object other) =>
+ other is SimpleConversion && other.functionName == functionName;
+
+ @override
+ String toCode({required String name, required ErrorCodeParameterType type}) =>
+ 'conversions.$functionName($name)';
+}
diff --git a/pkg/front_end/tool/generate_messages_lib.dart b/pkg/front_end/tool/generate_messages_lib.dart
index 23c391a..1f06c67 100644
--- a/pkg/front_end/tool/generate_messages_lib.dart
+++ b/pkg/front_end/tool/generate_messages_lib.dart
@@ -5,48 +5,9 @@
/// @docImport 'package:front_end/src/codes/type_labeler.dart';
library;
-import 'dart:io' show File, exitCode;
+import 'dart:io' show exitCode;
-import "package:_fe_analyzer_shared/src/messages/severity.dart"
- show severityEnumNames;
-import 'package:yaml/yaml.dart' show loadYaml;
-
-/// Map assigning each possible template parameter a [_TemplateParameterType].
-///
-/// TODO(paulberry): Change the format of `messages.yaml` so that the template
-/// parameters, and their types, are stated explicitly, as they are in the
-/// analyzer's `messages.yaml` file. Then this constant will not be needed.
-const _templateParameterNameToType = {
- 'character': _TemplateParameterType.character,
- 'unicode': _TemplateParameterType.unicode,
- 'name': _TemplateParameterType.name,
- 'name2': _TemplateParameterType.name,
- 'name3': _TemplateParameterType.name,
- 'name4': _TemplateParameterType.name,
- 'nameOKEmpty': _TemplateParameterType.nameOKEmpty,
- 'names': _TemplateParameterType.names,
- 'lexeme': _TemplateParameterType.token,
- 'lexeme2': _TemplateParameterType.token,
- 'string': _TemplateParameterType.string,
- 'string2': _TemplateParameterType.string,
- 'string3': _TemplateParameterType.string,
- 'stringOKEmpty': _TemplateParameterType.stringOKEmpty,
- 'type': _TemplateParameterType.type,
- 'type2': _TemplateParameterType.type,
- 'type3': _TemplateParameterType.type,
- 'type4': _TemplateParameterType.type,
- 'uri': _TemplateParameterType.uri,
- 'uri2': _TemplateParameterType.uri,
- 'uri3': _TemplateParameterType.uri,
- 'count': _TemplateParameterType.int,
- 'count2': _TemplateParameterType.int,
- 'count3': _TemplateParameterType.int,
- 'count4': _TemplateParameterType.int,
- 'constant': _TemplateParameterType.constant,
- 'num1': _TemplateParameterType.num,
- 'num2': _TemplateParameterType.num,
- 'num3': _TemplateParameterType.num,
-};
+import 'package:analyzer_utilities/messages.dart';
Uri computeSharedGeneratedFile(Uri repoDir) {
return repoDir.resolve(
@@ -68,10 +29,6 @@
}
Messages generateMessagesFilesRaw(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();
@@ -111,40 +68,23 @@
int largestIndex = 0;
final indexNameMap = new Map<int, String>();
- List<String> keys = yaml.keys.cast<String>().toList()..sort();
+ List<String> keys = frontEndMessages.keys.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'];
+ var errorCodeInfo = frontEndMessages[name]!;
+ var index = errorCodeInfo.index;
if (index != null) {
- if (index is! int || index < 1) {
+ String? otherName = indexNameMap[index];
+ if (otherName != null) {
print(
- 'Error: Expected positive int for "index:" field in $name,'
- ' but found $index',
+ 'Error: The "index:" field must be unique, '
+ 'but is the same for $otherName and $name',
);
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;
- }
+ indexNameMap[index] = name;
+ if (largestIndex < index) {
+ largestIndex = index;
}
}
}
@@ -153,7 +93,7 @@
template = _TemplateCompiler(
name: name,
index: index,
- description: description,
+ errorCodeInfo: errorCodeInfo,
).compile();
} catch (e, st) {
Error.throwWithStackTrace('Error while compiling $name: $e', st);
@@ -190,10 +130,6 @@
return new Messages("$sharedMessages", "$cfeMessages");
}
-final RegExp placeholderPattern = new RegExp(
- "#\([-a-zA-Z0-9_]+\)(?:%\([0-9]*\)\.\([0-9]+\))?",
-);
-
/// Returns a fresh identifier that is not yet present in [usedNames], and adds
/// it to [usedNames].
///
@@ -214,183 +150,6 @@
Template(this.text, {this.isShared}) : assert(isShared != null);
}
-/// Information about how to convert the CFE's internal representation of a
-/// template parameter to a string.
-///
-/// Instances of this class should implement [==] and [hashCode] so that they
-/// can be used as keys in a [Map].
-sealed class _Conversion {
- /// Returns Dart code that applies the conversion to a template parameter
- /// having the given [name] and [type].
- ///
- /// If no conversion is needed, returns `null`.
- String? toCode({required String name, required _TemplateParameterType type});
-}
-
-/// A [_Conversion] that makes use of the [TypeLabeler] class.
-class _LabelerConversion implements _Conversion {
- /// The name of the [TypeLabeler] method to call.
- final String methodName;
-
- const _LabelerConversion(this.methodName);
-
- @override
- int get hashCode => Object.hash(runtimeType, methodName.hashCode);
-
- @override
- bool operator ==(Object other) =>
- other is _LabelerConversion && other.methodName == methodName;
-
- @override
- String toCode({required String name, required _TemplateParameterType type}) =>
- 'labeler.$methodName($name)';
-}
-
-/// A [_Conversion] that acts on [num], applying formatting parameters specified
-/// in the template.
-class _NumericConversion implements _Conversion {
- /// If non-null, the number of digits to show after the decimal point.
- final int? fractionDigits;
-
- /// The minimum number of characters of output to be generated.
- ///
- /// If the number does not require this many characters to display, extra
- /// padding characters are inserted to the left.
- final int padWidth;
-
- /// If `true`, '0' is used for padding (see [padWidth]); otherwise ' ' is
- /// used.
- final bool padWithZeros;
-
- _NumericConversion({
- required this.fractionDigits,
- required this.padWidth,
- required this.padWithZeros,
- });
-
- @override
- int get hashCode => Object.hash(
- runtimeType,
- fractionDigits.hashCode,
- padWidth.hashCode,
- padWithZeros.hashCode,
- );
-
- @override
- bool operator ==(Object other) =>
- other is _NumericConversion &&
- other.fractionDigits == fractionDigits &&
- other.padWidth == padWidth &&
- other.padWithZeros == padWithZeros;
-
- @override
- String? toCode({required String name, required _TemplateParameterType type}) {
- if (type != _TemplateParameterType.num) {
- throw 'format suffix may only be applied to parameters of type num';
- }
- return 'conversions.formatNumber($name, fractionDigits: $fractionDigits, '
- 'padWidth: $padWidth, padWithZeros: $padWithZeros)';
- }
-
- /// Creates a [_NumericConversion] from the given regular expression [match].
- ///
- /// [match] should be the result of matching [placeholderPattern] to the
- /// template string.
- ///
- /// Returns `null` if no special numeric conversion is needed.
- static _NumericConversion? from(Match match) {
- String? padding = match[2];
- String? fractionDigitsStr = match[3];
-
- int? fractionDigits = fractionDigitsStr == null
- ? null
- : int.parse(fractionDigitsStr);
- if (padding != null && padding.isNotEmpty) {
- return _NumericConversion(
- fractionDigits: fractionDigits,
- padWidth: int.parse(padding),
- padWithZeros: padding.startsWith('0'),
- );
- } else if (fractionDigits != null) {
- return _NumericConversion(
- fractionDigits: fractionDigits,
- padWidth: 0,
- padWithZeros: false,
- );
- } else {
- return null;
- }
- }
-}
-
-/// The result of parsing a [placeholderPattern] match in a template string.
-class _ParsedPlaceholder {
- /// The name of the template parameter.
- ///
- /// This is the identifier that immediately follows the `#`.
- final String name;
-
- /// The type of the corresponding template parameter.
- final _TemplateParameterType templateParameterType;
-
- /// The conversion that should be applied to the template parameter.
- final _Conversion? conversion;
-
- /// Builds a [_ParsedPlaceholder] from the given [match] of
- /// [placeholderPattern].
- factory _ParsedPlaceholder.fromMatch(Match match) {
- String name = match[1]!;
-
- var templateParameterType = _templateParameterNameToType[name];
- if (templateParameterType == null) {
- throw "Unhandled placeholder in template: '$name'";
- }
-
- return _ParsedPlaceholder._(
- name: name,
- templateParameterType: templateParameterType,
- conversion:
- _NumericConversion.from(match) ?? templateParameterType.conversion,
- );
- }
-
- _ParsedPlaceholder._({
- required this.name,
- required this.templateParameterType,
- required this.conversion,
- });
-
- @override
- int get hashCode => Object.hash(name, templateParameterType, conversion);
-
- @override
- bool operator ==(Object other) =>
- other is _ParsedPlaceholder &&
- other.name == name &&
- other.templateParameterType == templateParameterType &&
- other.conversion == conversion;
-}
-
-/// A [_Conversion] that invokes a top level function via the `conversions`
-/// import prefix.
-class _SimpleConversion implements _Conversion {
- /// The name of the function to be invoked.
- final String functionName;
-
- const _SimpleConversion(this.functionName);
-
- @override
- int get hashCode => Object.hash(runtimeType, functionName.hashCode);
-
- @override
- bool operator ==(Object other) =>
- other is _SimpleConversion && other.functionName == functionName;
-
- @override
- String toCode({required String name, required _TemplateParameterType type}) =>
- 'conversions.$functionName($name)';
-}
-
class _TemplateCompiler {
final String name;
final int? index;
@@ -399,26 +158,24 @@
final List<String> analyzerCodes;
final String? severity;
- late final Set<_ParsedPlaceholder> parsedPlaceholders = placeholderPattern
+ late final Set<ParsedPlaceholder> parsedPlaceholders = placeholderPattern
.allMatches("$problemMessage\n${correctionMessage ?? ''}")
- .map(_ParsedPlaceholder.fromMatch)
+ .map(ParsedPlaceholder.fromMatch)
.toSet();
final List<String> withArgumentsStatements = [];
_TemplateCompiler({
required this.name,
required this.index,
- required Map<Object?, Object?> description,
- }) : problemMessage =
- _decodeMessage(description['problemMessage']) ??
- (throw 'Error: missing problemMessage'),
- correctionMessage = _decodeMessage(description['correctionMessage']),
- analyzerCodes = _decodeAnalyzerCode(description['analyzerCode']),
- severity = _decodeSeverity(description['severity']);
+ required FrontEndErrorCodeInfo errorCodeInfo,
+ }) : problemMessage = errorCodeInfo.problemMessage,
+ correctionMessage = errorCodeInfo.correctionMessage,
+ analyzerCodes = errorCodeInfo.analyzerCode,
+ severity = errorCodeInfo.cfeSeverity;
Template compile() {
bool hasLabeler = parsedPlaceholders.any(
- (p) => p.conversion is _LabelerConversion,
+ (p) => p.conversion is LabelerConversion,
);
bool canBeShared = !hasLabeler;
@@ -453,7 +210,7 @@
if (hasLabeler) {
withArgumentsStatements.add("TypeLabeler labeler = new TypeLabeler();");
}
- var interpolators = <_ParsedPlaceholder, String>{};
+ var interpolators = <ParsedPlaceholder, String>{};
for (var p in parsedPlaceholders) {
if (p.conversion?.toCode(name: p.name, type: p.templateParameterType)
case var conversion?) {
@@ -472,8 +229,7 @@
.replaceAll(r"$", r"\$")
.replaceAllMapped(
placeholderPattern,
- (Match m) =>
- "\${${interpolators[_ParsedPlaceholder.fromMatch(m)]}}",
+ (Match m) => "\${${interpolators[ParsedPlaceholder.fromMatch(m)]}}",
);
return "\"\"\"$text\"\"\"";
}
@@ -511,10 +267,10 @@
}
var positionalParameters = parsedPlaceholders
- .map((p) => '${p.templateParameterType.cfeType} ${p.name}')
+ .map((p) => '${p.templateParameterType.cfeName!} ${p.name}')
.toList();
var namedParameters = parsedPlaceholders
- .map((p) => 'required ${p.templateParameterType.cfeType} ${p.name}')
+ .map((p) => 'required ${p.templateParameterType.cfeName!} ${p.name}')
.toList();
var oldToNewArguments = parsedPlaceholders
.map((p) => '${p.name}: ${p.name}')
@@ -539,87 +295,4 @@
_withArguments$name(${oldToNewArguments.join(', ')});
""", isShared: canBeShared);
}
-
- static List<String> _decodeAnalyzerCode(Object? yamlEntry) {
- switch (yamlEntry) {
- case null:
- return const [];
- case String():
- return [yamlEntry];
- case List():
- return yamlEntry.cast<String>();
- default:
- throw 'Bad analyzerCode type: ${yamlEntry.runtimeType}';
- }
- }
-
- static String? _decodeMessage(Object? yamlEntry) {
- switch (yamlEntry) {
- case null:
- return null;
- case String():
- // Remove trailing whitespace. This is necessary for templates defined
- // with `|` (verbatim) as they always contain a trailing newline that we
- // don't want.
- return yamlEntry.trimRight();
- default:
- throw 'Bad message type: ${yamlEntry.runtimeType}';
- }
- }
-
- static String? _decodeSeverity(Object? yamlEntry) {
- switch (yamlEntry) {
- case null:
- return null;
- case String():
- return severityEnumNames[yamlEntry] ??
- (throw "Unknown severity '$yamlEntry'");
- default:
- throw 'Bad severity type: ${yamlEntry.runtimeType}';
- }
- }
-}
-
-/// Enum describing the types of template parameters supported by front_end
-/// diagnostic codes.
-///
-/// Each instance of this enum carries information about the type of the CFE's
-/// internal representation of the parameter and how to convert it to a string.
-enum _TemplateParameterType {
- character(
- cfeType: 'String',
- conversion: _SimpleConversion('validateCharacter'),
- ),
- unicode(cfeType: 'int', conversion: _SimpleConversion('codePointToUnicode')),
- name(
- cfeType: 'String',
- conversion: _SimpleConversion('validateAndDemangleName'),
- ),
- nameOKEmpty(
- cfeType: 'String',
- conversion: _SimpleConversion('nameOrUnnamed'),
- ),
- names(
- cfeType: 'List<String>',
- conversion: _SimpleConversion('validateAndItemizeNames'),
- ),
- string(cfeType: 'String', conversion: _SimpleConversion('validateString')),
- stringOKEmpty(
- cfeType: 'String',
- conversion: _SimpleConversion('stringOrEmpty'),
- ),
- token(cfeType: 'Token', conversion: _SimpleConversion('tokenToLexeme')),
- type(cfeType: 'DartType', conversion: _LabelerConversion('labelType')),
- uri(cfeType: 'Uri', conversion: _SimpleConversion('relativizeUri')),
- int(cfeType: 'int'),
- constant(
- cfeType: 'Constant',
- conversion: _LabelerConversion('labelConstant'),
- ),
- num(cfeType: 'num', conversion: _SimpleConversion('formatNumber'));
-
- final String cfeType;
- final _Conversion? conversion;
-
- const _TemplateParameterType({required this.cfeType, this.conversion});
}