[messages] Handle camelCase-formatted files.
Updates the logic in `pkg/analyzer_utilities` and `pkg/front_end/test/messages_suite.dart` for processing `messages.yaml` files so that: - Message keys in analyzer-style `messages.yaml` files can be `camelCase`, `lower_snake_case`, or `UPPER_SNAKE_CASE` (previously they could only be `lower_snake_case` or `UPPER_SNAKE_CASE`). - Message keys in CFE-style `messages.yaml` files can be either `camelCase` or `PascalCase` (previously they could only be `PascalCase`). - `sharedName` fields can be `camelCase`, `lower_snake_case`, or `UPPER_SNAKE_CASE` (previously they could only be `lower_snake_case` or `UPPER_SNAKE_CASE`). - `analyzerCode` fields in `pkg/_fe_analyzer_shared/messages.yaml` can be `ClassName.lower_snake_case`, `ClassName.UPPER_SNAKE_CASE`, or `camelCase`, where `ClassName` is ignored (previously they could only be `ClassName.lower_snake_case` or `ClassName.UPPER_SNAKE_CASE`). This paves the way for a follow-up CL in which all these fields and keys will be standardized to `camelCase`, and then the ability to specify them in `lower_snake_case` or `UPPER_SNAKE_CASE` will be removed. This will eliminate a significant inconsistency between the diagnostic code formats in the analyzer and the front end. Note that a few diagnostic names contain an underscore immediately followed by a digit in their snake case representation: - `final_not_initialized_constructor_1` - `final_not_initialized_constructor_2` - `final_not_initialized_constructor_3_plus` - `lines_longer_than_80_chars` When these are converted to `camelCase` form, the code generator will no longer know to introduce the underscores when converting them back to `snake_case` form (e.g. `finalNotInitializedConstructor1` will get converted to `final_not_initialized_constructor1`). The snake case forms are an important part of the customer facing API (since they are what is accepted in `// ignore:` comments), so in order to preserve the existing snake case names, a hardcoded map is introduced, `_snakeCaseExceptions`. Change-Id: I6a6a696444ccd92dd6574a7cde08da88ac5a7135 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/466540 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
@@ -196,7 +196,9 @@ List<M> decodeAnalyzerMessagesYaml<M extends AnalyzerMessage>(
|
||||
key: keyNode,
|
||||
value: diagnosticValue,
|
||||
decoder: (messageYaml) {
|
||||
var analyzerCode = DiagnosticCodeName(snakeCaseName: diagnosticName);
|
||||
var analyzerCode = DiagnosticCodeName.fromCamelOrSnakeCase(
|
||||
diagnosticName,
|
||||
);
|
||||
return decodeMessage(
|
||||
messageYaml,
|
||||
analyzerCode: analyzerCode,
|
||||
|
||||
@@ -153,7 +153,7 @@ abstract class CfeStyleMessage extends Message {
|
||||
///
|
||||
/// This is the key corresponding to the diagnostic's entry in
|
||||
/// `messages.yaml`.
|
||||
final String frontEndCode;
|
||||
final DiagnosticCodeName frontEndCode;
|
||||
|
||||
CfeStyleMessage(MessageYaml messageYaml)
|
||||
: cfeSeverity = messageYaml.get(
|
||||
@@ -172,7 +172,18 @@ abstract class CfeStyleMessage extends Message {
|
||||
},
|
||||
ifAbsent: () => null,
|
||||
),
|
||||
frontEndCode = messageYaml.keyString,
|
||||
frontEndCode = switch (messageYaml.keyString) {
|
||||
var s when s.isCamelCase => DiagnosticCodeName.fromCamelCase(
|
||||
messageYaml.keyString,
|
||||
),
|
||||
var s when s.isPascalCase => DiagnosticCodeName.fromPascalCase(
|
||||
messageYaml.keyString,
|
||||
),
|
||||
_ => throw LocatedError(
|
||||
'Front end codes must be camelCase or PascalCase',
|
||||
span: messageYaml.keySpan,
|
||||
),
|
||||
},
|
||||
super(messageYaml, requireProblemMessage: true) {
|
||||
// Ignore extra keys related to front end example-based tests.
|
||||
messageYaml.allowExtraKeys({
|
||||
@@ -211,24 +222,74 @@ sealed class Conversion {
|
||||
/// This class implements [Comparable], so lists of it can be safely
|
||||
/// [List.sort]ed.
|
||||
class DiagnosticCodeName implements Comparable<DiagnosticCodeName> {
|
||||
/// The diagnostic name.
|
||||
/// Exceptions to the usual rules for converting diagnostic code names from
|
||||
/// camel case to snake case.
|
||||
///
|
||||
/// The diagnostic name is in "snake case", meaning it consists of words
|
||||
/// separated by underscores. Those words might be lower case or upper case.
|
||||
/// Normally, diagnostic code names are converted from camel case to snake
|
||||
/// case using [StringExtension.toSnakeCase]. But in rare situations when the
|
||||
/// name contains numbers, this can produce results that aren't ideal. Rather
|
||||
/// than try to fix these rare situations in a general fashion, it's easier to
|
||||
/// just have an explicit map of the problematic names, with their preferred
|
||||
/// snake case forms.
|
||||
static const Map<String, String> _snakeCaseExceptions = {
|
||||
'finalNotInitializedConstructor1': 'final_not_initialized_constructor_1',
|
||||
'finalNotInitializedConstructor2': 'final_not_initialized_constructor_2',
|
||||
'finalNotInitializedConstructor3Plus':
|
||||
'final_not_initialized_constructor_3_plus',
|
||||
'linesLongerThan80Chars': 'lines_longer_than_80_chars',
|
||||
};
|
||||
|
||||
/// The diagnostic name, as a "snake case" name (words separated by
|
||||
/// underscores).
|
||||
///
|
||||
/// The name might be lower case or upper case.
|
||||
// TODO(paulberry): change `messages.yaml` to consistently use lower snake
|
||||
// case, and remove [lowerSnakeCaseName].
|
||||
final String snakeCaseName;
|
||||
|
||||
DiagnosticCodeName({required this.snakeCaseName});
|
||||
/// The diagnostic name, as a "camel case" name (lower case word followed by
|
||||
/// capitalized words, with no separation between words).
|
||||
final String camelCaseName;
|
||||
|
||||
DiagnosticCodeName.fromCamelCase(this.camelCaseName)
|
||||
: snakeCaseName =
|
||||
_snakeCaseExceptions[camelCaseName] ?? camelCaseName.toSnakeCase() {
|
||||
if (snakeCaseName.toLowerCase() != snakeCaseName) {
|
||||
throw 'Snake case name ${json.encode(snakeCaseName)} is not all lower '
|
||||
'case';
|
||||
}
|
||||
if (snakeCaseName.toCamelCase() != camelCaseName) {
|
||||
throw 'Round-trip conversion from ${json.encode(camelCaseName)} to snake '
|
||||
'case and back produces ${json.encode(snakeCaseName.toCamelCase())}';
|
||||
}
|
||||
}
|
||||
|
||||
factory DiagnosticCodeName.fromCamelOrSnakeCase(String value) =>
|
||||
value.isCamelCase
|
||||
? DiagnosticCodeName.fromCamelCase(value)
|
||||
: DiagnosticCodeName.fromSnakeCase(value);
|
||||
|
||||
factory DiagnosticCodeName.fromPascalCase(String pascalCaseName) {
|
||||
var snakeCaseName = pascalCaseName.toSnakeCase();
|
||||
var camelCaseName = snakeCaseName.toPascalCase();
|
||||
return DiagnosticCodeName._(
|
||||
snakeCaseName: snakeCaseName,
|
||||
camelCaseName: camelCaseName,
|
||||
);
|
||||
}
|
||||
|
||||
DiagnosticCodeName.fromSnakeCase(this.snakeCaseName)
|
||||
: camelCaseName = snakeCaseName.toCamelCase();
|
||||
|
||||
DiagnosticCodeName._({
|
||||
required this.snakeCaseName,
|
||||
required this.camelCaseName,
|
||||
});
|
||||
|
||||
/// The string that should be generated into analyzer source code to refer to
|
||||
/// this diagnostic code.
|
||||
String get analyzerCodeReference => ['diag', camelCaseName].join('.');
|
||||
|
||||
/// The diagnostic name, converted to camel case.
|
||||
String get camelCaseName => snakeCaseName.toCamelCase();
|
||||
|
||||
@override
|
||||
int get hashCode => snakeCaseName.hashCode;
|
||||
|
||||
@@ -410,7 +471,7 @@ class DiagnosticTables {
|
||||
activeMessagesByPackage = {};
|
||||
|
||||
DiagnosticTables._(List<Message> messages) {
|
||||
var frontEndCodeDuplicateChecker = _DuplicateChecker<String>(
|
||||
var frontEndCodeDuplicateChecker = _DuplicateChecker<DiagnosticCodeName>(
|
||||
kind: 'Front end code',
|
||||
);
|
||||
var analyzerCodeDuplicateChecker = _DuplicateChecker<DiagnosticCodeName>(
|
||||
@@ -602,7 +663,7 @@ abstract class Message {
|
||||
) ??
|
||||
[],
|
||||
sharedName = switch (messageYaml.getOptionalString('sharedName')) {
|
||||
var s? => DiagnosticCodeName(snakeCaseName: s),
|
||||
var s? => DiagnosticCodeName.fromCamelOrSnakeCase(s),
|
||||
null => null,
|
||||
},
|
||||
removedIn = messageYaml.getOptionalString('removedIn'),
|
||||
@@ -949,10 +1010,13 @@ class SharedMessage extends CfeStyleMessage with MessageWithAnalyzerCode {
|
||||
switch (s.split('.')) {
|
||||
case [_, var snakeCaseName]
|
||||
when snakeCaseName == snakeCaseName.toUpperCase():
|
||||
return DiagnosticCodeName(snakeCaseName: snakeCaseName);
|
||||
return DiagnosticCodeName.fromSnakeCase(snakeCaseName);
|
||||
case [var camelCaseName] when camelCaseName.isCamelCase:
|
||||
return DiagnosticCodeName.fromCamelCase(camelCaseName);
|
||||
}
|
||||
}
|
||||
throw 'Analyzer codes must take the form ClassName.DIAGNOSTIC_NAME.';
|
||||
throw 'Analyzer codes must be either camelCase names or must take the form '
|
||||
'ClassName.DIAGNOSTIC_NAME.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
|
||||
getMessageRelatedInformation;
|
||||
import 'package:_fe_analyzer_shared/src/messages/severity.dart'
|
||||
show CfeSeverity, severityEnumValues;
|
||||
import 'package:analyzer_utilities/extensions/string.dart';
|
||||
import 'package:front_end/src/api_prototype/compiler_options.dart'
|
||||
show CompilerOptions, parseExperimentalArguments, parseExperimentalFlags;
|
||||
import 'package:front_end/src/api_prototype/experimental_flags.dart'
|
||||
@@ -176,9 +177,11 @@ class MessageTestSuite extends ChainContext {
|
||||
File file = new File.fromUri(uri);
|
||||
String fileContent = file.readAsStringSync();
|
||||
YamlMap messages = loadYamlNode(fileContent, sourceUrl: uri) as YamlMap;
|
||||
for (String name in messages.keys) {
|
||||
for (String camelCaseName in messages.keys) {
|
||||
try {
|
||||
YamlMap messageNode = messages.nodes[name] as YamlMap;
|
||||
// TODO(paulberry): switch CFE to camelCase conventions.
|
||||
var name = camelCaseName.toSnakeCase().toPascalCase();
|
||||
YamlMap messageNode = messages.nodes[camelCaseName] as YamlMap;
|
||||
dynamic message = messageNode.value;
|
||||
if (message is String) continue;
|
||||
|
||||
@@ -552,7 +555,7 @@ class MessageTestSuite extends ChainContext {
|
||||
),
|
||||
);
|
||||
} catch (e, st) {
|
||||
Error.throwWithStackTrace('While processing $name: $e', st);
|
||||
Error.throwWithStackTrace('While processing $camelCaseName: $e', st);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -631,6 +631,7 @@ parallax
|
||||
parameterized
|
||||
partfoo
|
||||
party
|
||||
pascal
|
||||
pause
|
||||
paused
|
||||
pays
|
||||
|
||||
@@ -152,7 +152,7 @@ class _TemplateCompiler {
|
||||
_TemplateCompiler({
|
||||
required this.message,
|
||||
required this.pseudoSharedCodeValues,
|
||||
}) : name = message.frontEndCode,
|
||||
}) : name = message.frontEndCode.pascalCaseName,
|
||||
problemMessage = message.problemMessage,
|
||||
correctionMessage = message.correctionMessage,
|
||||
severity = message.cfeSeverity,
|
||||
|
||||
Reference in New Issue
Block a user