blob: 9da6ca2471c458215c4cf5096c4c574b8051020c [file] [log] [blame]
// Copyright (c) 2022, 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 'package:analysis_server/src/services/correction/dart/data_driven.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/lint/registry.dart';
import 'package:analyzer_utilities/package_root.dart' as package_root;
import 'package:linter/src/rules.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package:yaml/yaml.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(VerifyErrorFixStatusTest);
});
}
@reflectiveTest
class VerifyErrorFixStatusTest {
PhysicalResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE;
void test_statusFile() {
var statusInfo = _statusInfo();
var errorCodeNames = _errorCodeNames();
var lintRuleNames = _lintRuleNames();
var errorData = _ErrorData();
for (var code in errorCodeValues) {
var name = code.uniqueName;
if (name.startsWith('TodoCode.')) {
// To-do codes are ignored.
} else {
var info = statusInfo.nodes[name];
if (info == null) {
errorData.codesWithNoEntry.add(name);
} else if (info is YamlMap) {
var markedAsHavingFix = info['status'] == 'hasFix';
var hasFix = _hasCodeFix(code);
if (hasFix) {
if (!markedAsHavingFix) {
errorData.codesWithFixes.add(name);
}
} else {
if (markedAsHavingFix) {
errorData.codesWithoutFixes.add(name);
}
}
}
}
}
for (var name in lintRuleNames) {
var info = statusInfo.nodes[name];
if (info == null) {
errorData.codesWithNoEntry.add(name);
} else if (info is YamlMap) {
var markedAsHavingFix = info['status'] == 'hasFix';
var hasFix = _hasLintFix(name);
if (hasFix) {
if (!markedAsHavingFix) {
errorData.codesWithFixes.add(name);
}
} else {
if (markedAsHavingFix) {
errorData.codesWithoutFixes.add(name);
}
}
}
}
for (var key in statusInfo.keys) {
if (key is String) {
if (!errorCodeNames.contains(key) && !lintRuleNames.contains(key)) {
errorData.entriesWithNoCode.add(key);
}
}
}
if (errorData.isNotEmpty) {
fail(_failureMessage(errorData));
}
}
/// Return the unique names of the error codes.
Set<String> _errorCodeNames() {
var codes = errorCodeValues;
var codeNames = <String>{};
for (var code in codes) {
codeNames.add(code.uniqueName);
}
return codeNames;
}
/// Return a failure message composed from the given lists.
String _failureMessage(_ErrorData errorData) {
var buffer = StringBuffer();
var needsBlankLine = false;
if (errorData.codesWithNoEntry.isNotEmpty) {
buffer.writeln('Add the following entries:');
buffer.writeln();
for (var code in errorData.codesWithNoEntry) {
buffer.writeln('$code:');
buffer.writeln(' status: needsEvaluation');
}
needsBlankLine = true;
}
if (errorData.entriesWithNoCode.isNotEmpty) {
if (needsBlankLine) {
buffer.writeln();
}
buffer.writeln('Remove the following entries:');
for (var code in errorData.entriesWithNoCode) {
buffer.writeln('- $code');
}
needsBlankLine = true;
}
if (errorData.codesWithFixes.isNotEmpty) {
if (needsBlankLine) {
buffer.writeln();
}
buffer.writeln('Mark the following entries as having fixes:');
for (var code in errorData.codesWithFixes) {
buffer.writeln('- $code');
}
needsBlankLine = true;
}
if (errorData.codesWithoutFixes.isNotEmpty) {
if (needsBlankLine) {
buffer.writeln();
}
buffer.writeln('Mark the following entries as not having fixes:');
for (var code in errorData.codesWithoutFixes) {
buffer.writeln('- $code');
}
needsBlankLine = true;
}
return buffer.toString();
}
/// Return `true` if the given error [code] has a fix associated with it.
bool _hasCodeFix(ErrorCode code) {
var producers = FixProcessor.nonLintProducerMap[code];
if (producers != null) {
return true;
}
var multiProducers = FixProcessor.nonLintMultiProducerMap[code];
if (multiProducers != null) {
for (var producer in multiProducers) {
if (producer is! DataDriven) {
return true;
}
}
}
return false;
}
/// Return `true` if the lint with the given name has a fix associated with
/// it.
bool _hasLintFix(String codeName) {
var name = codeName.substring('LintCode.'.length);
var producers = FixProcessor.lintProducerMap[name];
return producers != null;
}
/// Return the unique names of the lint rules.
Set<String> _lintRuleNames() {
registerLintRules();
var ruleNames = <String>{};
for (var rule in Registry.ruleRegistry.rules) {
for (var code in rule.lintCodes) {
ruleNames.add(code.uniqueName);
}
}
return ruleNames;
}
/// Return the path to the file containing the status information.
String _statusFilePath() {
var pathContext = resourceProvider.pathContext;
var packageRoot = pathContext.normalize(package_root.packageRoot);
return pathContext.join(packageRoot, 'analysis_server', 'lib', 'src',
'services', 'correction', 'error_fix_status.yaml');
}
/// Return the content of the file containing the status information, parsed
/// as a YAML map.
YamlMap _statusInfo() {
var statusFile = resourceProvider.getFile(_statusFilePath());
var document = loadYamlDocument(statusFile.readAsStringSync());
var statusInfo = document.contents;
if (statusInfo is! YamlMap) {
fail('Expected a YamlMap, found ${statusInfo.runtimeType}');
}
return statusInfo;
}
}
class _ErrorData {
final List<String> codesWithFixes = [];
final List<String> codesWithNoEntry = [];
final List<String> codesWithoutFixes = [];
final List<String> entriesWithNoCode = [];
bool get isNotEmpty =>
codesWithFixes.isNotEmpty ||
codesWithNoEntry.isNotEmpty ||
codesWithoutFixes.isNotEmpty ||
entriesWithNoCode.isNotEmpty;
}