blob: ab44cc8359d44e6eb206379b7d99a1547329c00b [file] [log] [blame]
// Copyright (c) 2023, 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:convert' as json;
import 'dart:io';
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
import 'package:kernel/ast.dart';
import '../base/command_line_reporting.dart';
import '../codes/cfe_codes.dart';
import 'analysis_helper.dart';
/// [AnalysisVisitor] that supports tracking error/problem occurrences in an
/// allowed list file.
class VerifyingAnalysis extends AnalysisVisitor {
final String? _allowedListPath;
Map _expectedJson = {};
VerifyingAnalysis(
DiagnosticMessageHandler onDiagnostic,
Component component,
this._allowedListPath,
UriFilter? analyzedUrisFilter,
) : super(onDiagnostic, component, analyzedUrisFilter);
void run({bool verbose = false, bool generate = false}) {
if (!generate && _allowedListPath != null) {
File file = new File(_allowedListPath);
if (file.existsSync()) {
try {
_expectedJson = json.jsonDecode(file.readAsStringSync());
} catch (e) {
throw 'Error reading allowed list from $_allowedListPath: $e';
}
}
}
component.accept(this);
if (generate && _allowedListPath != null) {
Map<String, Map<String, int>> actualJson = {};
forEachMessage((
String uri,
Map<String, List<FormattedMessage>> actualMessagesMap,
) {
Map<String, int> map = {};
actualMessagesMap.forEach((
String message,
List<FormattedMessage> actualMessages,
) {
map[message] = actualMessages.length;
});
actualJson[uri] = map;
});
new File(_allowedListPath).writeAsStringSync(
new json.JsonEncoder.withIndent(' ').convert(actualJson),
);
return;
}
int errorCount = 0;
_expectedJson.forEach((uri, expectedMessages) {
Map<String, List<FormattedMessage>>? actualMessagesMap =
getMessagesForUri(uri);
if (actualMessagesMap == null) {
print(
"Error: Allowed-listing of uri '$uri' isn't used. "
"Remove it from the allowed-list.",
);
errorCount++;
} else {
expectedMessages.forEach((expectedMessage, expectedCount) {
List<FormattedMessage>? actualMessages =
actualMessagesMap[expectedMessage];
if (actualMessages == null) {
print(
"Error: Allowed-listing of message '$expectedMessage' "
"in uri '$uri' isn't used. Remove it from the allowed-list.",
);
errorCount++;
} else {
int actualCount = actualMessages.length;
if (actualCount != expectedCount) {
print(
"Error: Unexpected count of allowed message "
"'$expectedMessage' in uri '$uri'. "
"Expected $expectedCount, actual $actualCount:",
);
print(
'----------------------------------------------------------',
);
for (FormattedMessage message in actualMessages) {
onDiagnostic(message);
}
print(
'----------------------------------------------------------',
);
errorCount++;
}
}
});
actualMessagesMap.forEach((
String message,
List<FormattedMessage> actualMessages,
) {
if (!expectedMessages.containsKey(message)) {
for (FormattedMessage message in actualMessages) {
onDiagnostic(message);
errorCount++;
}
}
});
}
});
forEachMessage((
String uri,
Map<String, List<FormattedMessage>> actualMessagesMap,
) {
if (!_expectedJson.containsKey(uri)) {
actualMessagesMap.forEach((
String message,
List<FormattedMessage> actualMessages,
) {
for (FormattedMessage message in actualMessages) {
onDiagnostic(message);
errorCount++;
}
});
}
});
if (errorCount != 0) {
print('$errorCount error(s) found.');
print("""
********************************************************************************
* Unexpected code patterns found by test:
*
* ${relativizeUri(Platform.script)}
*
* Please address the reported errors, or, if the errors are as expected, run
*
* dart ${relativizeUri(Platform.script)} -g
*
* to update the expectation file.
********************************************************************************
""");
exit(-1);
}
if (verbose) {
forEachMessage((
String uri,
Map<String, List<FormattedMessage>> actualMessagesMap,
) {
actualMessagesMap.forEach((
String message,
List<FormattedMessage> actualMessages,
) {
for (FormattedMessage message in actualMessages) {
// TODO(johnniwinther): It is unnecessarily complicated to just
// add ' (allowed)' to an existing message!
LocatedMessage locatedMessage = message.locatedMessage;
String newMessageText =
'${locatedMessage.messageObject.problemMessage} (allowed)';
message = locatedMessage.withFormatting(
formatWithLocationNoSdk(
new LocatedMessage(
locatedMessage.uri,
locatedMessage.charOffset,
locatedMessage.length,
new Message(
locatedMessage.messageObject.code,
problemMessage: newMessageText,
correctionMessage:
locatedMessage.messageObject.correctionMessage,
arguments: locatedMessage.messageObject.arguments,
),
),
CfeSeverity.warning,
location: new Location(
message.uri!,
message.line,
message.column,
),
uriToSource: component.uriToSource,
),
message.line,
message.column,
CfeSeverity.warning,
[],
);
onDiagnostic(message);
}
});
});
} else {
int total = 0;
forEachMessage((
String uri,
Map<String, List<FormattedMessage>> actualMessagesMap,
) {
int count = 0;
actualMessagesMap.forEach((
String message,
List<FormattedMessage> actualMessages,
) {
count += actualMessages.length;
});
print('${count} error(s) allowed in $uri');
total += count;
});
if (total > 0) {
print('${total} error(s) allowed in total.');
print('Use option -v to see error details.');
}
}
}
void registerError(TreeNode node, String message) {
registerMessage(node, message);
}
}