9f986d2501
Change-Id: Iffe4370431587e46a141ddc72a86ceec29c163b2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176486 Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com>
206 lines
7.0 KiB
Dart
206 lines
7.0 KiB
Dart
// Copyright (c) 2017, 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' as io;
|
|
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/ast/visitor.dart';
|
|
import 'package:analyzer_utilities/package_root.dart' as package_root;
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import '../../generated/parser_test_base.dart';
|
|
|
|
main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(AbstractRecoveryTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class AbstractRecoveryTest extends FastaParserTestCase {
|
|
/// Given the path to the file containing the declaration of the fasta Parser,
|
|
/// return a set containing the names of all the messages and templates that
|
|
/// are referenced (presumably because they are being generated) within that
|
|
/// file.
|
|
Set<String> getGeneratedNames(String parserPath) {
|
|
String content = io.File(parserPath).readAsStringSync();
|
|
CompilationUnit unit = parseCompilationUnit(content);
|
|
expect(unit, isNotNull);
|
|
GeneratedCodesVisitor visitor = GeneratedCodesVisitor();
|
|
unit.accept(visitor);
|
|
return visitor.generatedNames;
|
|
}
|
|
|
|
/// Given the path to the file 'messages.yaml', return a list of the top-level
|
|
/// keys defined in that file that define an 'analyzerCode'.
|
|
List<String> getMappedCodes(String messagesPath) {
|
|
String content = io.File(messagesPath).readAsStringSync();
|
|
YamlDocument document = loadYamlDocument(content);
|
|
expect(document, isNotNull);
|
|
Set<String> codes = <String>{};
|
|
YamlNode contents = document.contents;
|
|
if (contents is YamlMap) {
|
|
for (String name in contents.keys) {
|
|
Object value = contents[name];
|
|
if (value is YamlMap) {
|
|
if (value['analyzerCode'] != null) {
|
|
codes.add(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return codes.toList();
|
|
}
|
|
|
|
/// Given the path to the file 'messages.yaml', return a list of the analyzer
|
|
/// codes defined in that file.
|
|
List<String> getReferencedCodes(String messagesPath) {
|
|
String content = io.File(messagesPath).readAsStringSync();
|
|
YamlDocument document = loadYamlDocument(content);
|
|
expect(document, isNotNull);
|
|
Set<String> codes = <String>{};
|
|
YamlNode contents = document.contents;
|
|
if (contents is YamlMap) {
|
|
for (String name in contents.keys) {
|
|
Object value = contents[name];
|
|
if (value is YamlMap) {
|
|
var code = value['analyzerCode']?.toString();
|
|
if (code != null) {
|
|
codes.add(code);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return codes.toList();
|
|
}
|
|
|
|
/// Given the path to the file containing the declaration of the AstBuilder,
|
|
/// return a list of the analyzer codes that are translated by the builder.
|
|
List<String> getTranslatedCodes(String astBuilderPath) {
|
|
String content = io.File(astBuilderPath).readAsStringSync();
|
|
CompilationUnit unit = parseCompilationUnit(content);
|
|
var astBuilder = unit.declarations[0] as ClassDeclaration;
|
|
var method = astBuilder.members
|
|
.whereType<MethodDeclaration>()
|
|
.firstWhere((x) => x.name.name == 'reportMessage');
|
|
SwitchStatement statement = (method.body as BlockFunctionBody)
|
|
.block
|
|
.statements
|
|
.whereType<SwitchStatement>()
|
|
.first;
|
|
expect(statement, isNotNull);
|
|
List<String> codes = <String>[];
|
|
for (SwitchMember member in statement.members) {
|
|
if (member is SwitchCase) {
|
|
codes.add((member.expression as StringLiteral).stringValue!);
|
|
}
|
|
}
|
|
return codes;
|
|
}
|
|
|
|
@failingTest
|
|
test_mappedMessageCoverage() {
|
|
String frontEndPath = path.join(package_root.packageRoot, 'front_end');
|
|
String parserPath =
|
|
path.join(frontEndPath, 'lib', 'src', 'fasta', 'parser', 'parser.dart');
|
|
Set<String> generatedNames = getGeneratedNames(parserPath);
|
|
|
|
String messagesPath = path.join(frontEndPath, 'messages.yaml');
|
|
List<String> mappedCodes = getMappedCodes(messagesPath);
|
|
|
|
generatedNames.removeAll(mappedCodes);
|
|
if (generatedNames.isEmpty) {
|
|
return;
|
|
}
|
|
List<String> sortedNames = generatedNames.toList()..sort();
|
|
StringBuffer buffer = StringBuffer();
|
|
buffer.writeln('Generated parser errors without analyzer codes:');
|
|
for (String name in sortedNames) {
|
|
buffer.write(' ');
|
|
buffer.writeln(name);
|
|
}
|
|
fail(buffer.toString());
|
|
}
|
|
|
|
@failingTest
|
|
test_translatedMessageCoverage() {
|
|
String analyzerPath = path.join(package_root.packageRoot, 'analyzer');
|
|
String astBuilderPath =
|
|
path.join(analyzerPath, 'lib', 'src', 'fasta', 'error_converter.dart');
|
|
List<String> translatedCodes = getTranslatedCodes(astBuilderPath);
|
|
|
|
String messagesPath =
|
|
path.join(path.dirname(analyzerPath), 'front_end', 'messages.yaml');
|
|
List<String> referencedCodes = getReferencedCodes(messagesPath);
|
|
|
|
List<String> untranslated = <String>[];
|
|
for (String referencedCode in referencedCodes) {
|
|
if (!translatedCodes.contains(referencedCode)) {
|
|
untranslated.add(referencedCode);
|
|
}
|
|
}
|
|
StringBuffer buffer = StringBuffer();
|
|
if (untranslated.isNotEmpty) {
|
|
buffer
|
|
.writeln('Analyzer codes used in messages.yaml but not translated:');
|
|
for (String code in untranslated) {
|
|
buffer.write(' ');
|
|
buffer.writeln(code);
|
|
}
|
|
buffer.write(
|
|
'Add a case for these codes to FastaErrorReporter.reportError.');
|
|
}
|
|
|
|
List<String> unreferenced = <String>[];
|
|
for (String translatedCode in translatedCodes) {
|
|
if (!referencedCodes.contains(translatedCode)) {
|
|
unreferenced.add(translatedCode);
|
|
}
|
|
}
|
|
if (untranslated.isNotEmpty) {
|
|
if (buffer.isNotEmpty) {
|
|
buffer.writeln();
|
|
buffer.writeln();
|
|
}
|
|
buffer.writeln(
|
|
'Analyzer codes that are translated but not used in messages.yaml:');
|
|
for (String code in unreferenced) {
|
|
buffer.write(' ');
|
|
buffer.writeln(code);
|
|
}
|
|
buffer.write('Remove the cases for these codes from '
|
|
'FastaErrorReporter.reportMessage.');
|
|
}
|
|
if (buffer.isNotEmpty) {
|
|
fail(buffer.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A visitor that gathers the names of all the message codes that are generated
|
|
/// in the visited AST. This assumes that the codes are accessed via the prefix
|
|
/// 'fasta'.
|
|
class GeneratedCodesVisitor extends RecursiveAstVisitor {
|
|
/// The names of the message codes that are generated in the visited AST.
|
|
Set<String> generatedNames = <String>{};
|
|
|
|
@override
|
|
visitPrefixedIdentifier(PrefixedIdentifier node) {
|
|
if (node.prefix.name == 'fasta') {
|
|
String name = node.identifier.name;
|
|
if (name.startsWith('message')) {
|
|
name = name.substring(7);
|
|
} else if (name.startsWith('template')) {
|
|
name = name.substring(8);
|
|
} else {
|
|
return;
|
|
}
|
|
generatedNames.add(name);
|
|
}
|
|
}
|
|
}
|