| // 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 = {}; | |
| new( | |
| 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); | |
| } | |
| } |