| // Copyright (c) 2021, 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'; |
| import 'dart:io'; |
| |
| import 'package:analyzer_testing/package_root.dart' as pkg_root; |
| import 'package:analyzer_utilities/messages.dart'; |
| import 'package:analyzer_utilities/tools.dart'; |
| import 'package:path/path.dart'; |
| import 'package:yaml/yaml.dart' show loadYaml, YamlMap; |
| |
| const codesFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/error/codes.g.dart', |
| parentLibrary: 'package:analyzer/src/error/codes.dart', |
| ); |
| |
| /// Information about all the classes derived from `DiagnosticCode` that are |
| /// code-generated based on the contents of the analyzer and front end |
| /// `messages.yaml` files. |
| const List<ErrorClassInfo> errorClasses = [ |
| ErrorClassInfo( |
| file: optionCodesFile, |
| name: 'AnalysisOptionsErrorCode', |
| type: 'COMPILE_TIME_ERROR', |
| severity: 'ERROR', |
| ), |
| ErrorClassInfo( |
| file: optionCodesFile, |
| name: 'AnalysisOptionsWarningCode', |
| type: 'STATIC_WARNING', |
| severity: 'WARNING', |
| ), |
| ErrorClassInfo( |
| file: codesFile, |
| name: 'CompileTimeErrorCode', |
| type: 'COMPILE_TIME_ERROR', |
| ), |
| ErrorClassInfo( |
| file: scannerErrorFile, |
| name: 'ScannerErrorCode', |
| type: 'SYNTACTIC_ERROR', |
| ), |
| ErrorClassInfo( |
| file: codesFile, |
| name: 'StaticWarningCode', |
| type: 'STATIC_WARNING', |
| severity: 'WARNING', |
| ), |
| ErrorClassInfo( |
| file: codesFile, |
| name: 'WarningCode', |
| type: 'STATIC_WARNING', |
| severity: 'WARNING', |
| ), |
| ErrorClassInfo( |
| file: ffiCodesFile, |
| name: 'FfiCode', |
| type: 'COMPILE_TIME_ERROR', |
| ), |
| ErrorClassInfo(file: hintCodesFile, name: 'HintCode', type: 'HINT'), |
| ErrorClassInfo( |
| file: syntacticErrorsFile, |
| name: 'ParserErrorCode', |
| type: 'SYNTACTIC_ERROR', |
| severity: 'ERROR', |
| includeCfeMessages: true, |
| deprecatedSnakeCaseNames: { |
| 'UNEXPECTED_TOKEN', // Referenced by `package:dart_style`. |
| }, |
| ), |
| ErrorClassInfo( |
| file: manifestWarningCodeFile, |
| name: 'ManifestWarningCode', |
| type: 'STATIC_WARNING', |
| severity: 'WARNING', |
| ), |
| ErrorClassInfo( |
| file: pubspecWarningCodeFile, |
| name: 'PubspecWarningCode', |
| type: 'STATIC_WARNING', |
| severity: 'WARNING', |
| ), |
| ErrorClassInfo( |
| file: todoCodesFile, |
| name: 'TodoCode', |
| type: 'TODO', |
| severity: 'INFO', |
| comment: ''' |
| The error code indicating a marker in code for work that needs to be finished |
| or revisited. |
| ''', |
| ), |
| ErrorClassInfo( |
| file: transformSetErrorCodeFile, |
| name: 'TransformSetErrorCode', |
| type: 'COMPILE_TIME_ERROR', |
| severity: 'ERROR', |
| includeInDiagnosticCodeValues: false, |
| comment: ''' |
| An error code representing a problem in a file containing an encoding of a |
| transform set. |
| ''', |
| ), |
| ]; |
| |
| const ffiCodesFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/dart/error/ffi_code.g.dart', |
| parentLibrary: 'package:analyzer/src/dart/error/ffi_code.dart', |
| ); |
| |
| const hintCodesFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/dart/error/hint_codes.g.dart', |
| parentLibrary: 'package:analyzer/src/dart/error/hint_codes.dart', |
| ); |
| |
| const manifestWarningCodeFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/manifest/manifest_warning_code.g.dart', |
| parentLibrary: 'package:analyzer/src/manifest/manifest_warning_code.dart', |
| ); |
| |
| const optionCodesFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/analysis_options/error/option_codes.g.dart', |
| parentLibrary: |
| 'package:analyzer/src/analysis_options/error/option_codes.dart', |
| ); |
| |
| const pubspecWarningCodeFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/pubspec/pubspec_warning_code.g.dart', |
| parentLibrary: 'package:analyzer/src/pubspec/pubspec_warning_code.dart', |
| ); |
| |
| const scannerErrorFile = GeneratedErrorCodeFile( |
| path: '_fe_analyzer_shared/lib/src/scanner/errors.g.dart', |
| parentLibrary: 'package:_fe_analyzer_shared/src/scanner/errors.dart', |
| shouldUseExplicitNewOrConst: true, |
| ); |
| |
| const syntacticErrorsFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/dart/error/syntactic_errors.g.dart', |
| parentLibrary: 'package:analyzer/src/dart/error/syntactic_errors.dart', |
| ); |
| |
| const todoCodesFile = GeneratedErrorCodeFile( |
| path: 'analyzer/lib/src/dart/error/todo_codes.g.dart', |
| parentLibrary: 'package:analyzer/src/dart/error/todo_codes.dart', |
| ); |
| |
| const transformSetErrorCodeFile = GeneratedErrorCodeFile( |
| path: |
| 'analysis_server/lib/src/services/correction/fix/data_driven/' |
| 'transform_set_error_code.g.dart', |
| parentLibrary: |
| 'package:analysis_server/src/services/correction/fix/data_driven/' |
| 'transform_set_error_code.dart', |
| shouldIgnorePreferSingleQuotes: true, |
| ); |
| |
| /// Decoded messages from the analyzer's `messages.yaml` file. |
| final Map<String, Map<String, AnalyzerErrorCodeInfo>> analyzerMessages = |
| _loadAnalyzerMessages(); |
| |
| /// The path to the `analyzer` package. |
| final String analyzerPkgPath = normalize( |
| join(pkg_root.packageRoot, 'analyzer'), |
| ); |
| |
| /// A set of tables mapping between front end and analyzer error codes. |
| final CfeToAnalyzerErrorCodeTables cfeToAnalyzerErrorCodeTables = |
| CfeToAnalyzerErrorCodeTables._(frontEndMessages); |
| |
| /// The path to the `linter` package. |
| final String linterPkgPath = normalize(join(pkg_root.packageRoot, 'linter')); |
| |
| /// Decoded messages from the linter's `messages.yaml` file. |
| final Map<String, Map<String, AnalyzerErrorCodeInfo>> lintMessages = |
| _loadLintMessages(); |
| |
| /// Decodes a YAML object (obtained from a `messages.yaml` file) into a |
| /// two-level map of [ErrorCodeInfo], indexed first by class name and then by |
| /// error name. |
| Map<String, Map<String, AnalyzerErrorCodeInfo>> decodeAnalyzerMessagesYaml( |
| String packagePath, |
| ) { |
| var yaml = |
| loadYaml(File(join(packagePath, 'messages.yaml')).readAsStringSync()) |
| as Object?; |
| Never problem(String message) { |
| throw 'Problem in $packagePath/messages.yaml: $message'; |
| } |
| |
| var result = <String, Map<String, AnalyzerErrorCodeInfo>>{}; |
| if (yaml is! Map<Object?, Object?>) { |
| problem('root node is not a map'); |
| } |
| for (var classEntry in yaml.entries) { |
| var className = classEntry.key; |
| if (className is! String) { |
| problem('non-string class key ${json.encode(className)}'); |
| } |
| var classValue = classEntry.value; |
| if (classValue is! Map<Object?, Object?>) { |
| problem('value associated with class key $className is not a map'); |
| } |
| for (var errorEntry in classValue.entries) { |
| var errorName = errorEntry.key; |
| if (errorName is! String) { |
| problem( |
| 'in class $className, non-string error key ' |
| '${json.encode(errorName)}', |
| ); |
| } |
| var errorValue = errorEntry.value; |
| if (errorValue is! YamlMap) { |
| problem( |
| 'value associated with error $className.$errorName is not a ' |
| 'map', |
| ); |
| } |
| |
| AnalyzerErrorCodeInfo errorCodeInfo; |
| try { |
| errorCodeInfo = (result[className] ??= {})[errorName] = |
| AnalyzerErrorCodeInfo.fromYaml(errorValue); |
| } catch (e, st) { |
| Error.throwWithStackTrace( |
| 'while processing $className.$errorName, $e', |
| st, |
| ); |
| } |
| if (errorCodeInfo.hasPublishedDocs == null) { |
| problem('Missing hasPublishedDocs for $className.$errorName'); |
| } |
| |
| if (errorCodeInfo case AliasErrorCodeInfo(:var aliasFor)) { |
| var aliasForPath = aliasFor.split('.'); |
| if (aliasForPath.isEmpty) { |
| problem("The 'aliasFor' value at '$className.$errorName is empty"); |
| } |
| var node = yaml; |
| for (var key in aliasForPath) { |
| var value = node[key]; |
| if (value is! Map<Object?, Object?>) { |
| problem( |
| 'No Map value at "$aliasFor", aliased from ' |
| '$className.$errorName', |
| ); |
| } |
| node = value; |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /// Loads analyzer messages from the analyzer's `messages.yaml` file. |
| Map<String, Map<String, AnalyzerErrorCodeInfo>> _loadAnalyzerMessages() => |
| decodeAnalyzerMessagesYaml(analyzerPkgPath); |
| |
| /// Loads linter messages from the linter's `messages.yaml` file. |
| Map<String, Map<String, AnalyzerErrorCodeInfo>> _loadLintMessages() => |
| decodeAnalyzerMessagesYaml(linterPkgPath); |
| |
| /// An [AnalyzerErrorCodeInfo] which is an alias for another, for incremental |
| /// deprecation purposes. |
| class AliasErrorCodeInfo extends AnalyzerErrorCodeInfo { |
| String aliasFor; |
| |
| AliasErrorCodeInfo._fromYaml(super.yaml, {required this.aliasFor}) |
| : super._fromYaml(); |
| |
| String get aliasForClass => aliasFor.split('.').first; |
| |
| String get aliasForFilePath => errorClasses |
| .firstWhere((element) => element.name == aliasForClass) |
| .file |
| .path; |
| |
| @override |
| void toAnalyzerCode( |
| ErrorClassInfo errorClassInfo, |
| String diagnosticCode, { |
| String? sharedNameReference, |
| required MemberAccumulator memberAccumulator, |
| }) { |
| var constant = StringBuffer(); |
| outputConstantHeader(constant); |
| constant.writeln(' static const $aliasForClass $diagnosticCode ='); |
| constant.writeln('$aliasFor;'); |
| memberAccumulator.constants[diagnosticCode] = constant.toString(); |
| } |
| } |
| |
| /// In-memory representation of error code information obtained from the |
| /// analyzer's `messages.yaml` file. |
| class AnalyzerErrorCodeInfo extends ErrorCodeInfo { |
| factory AnalyzerErrorCodeInfo.fromYaml(YamlMap yaml) { |
| if (yaml['aliasFor'] case var aliasFor?) { |
| return AliasErrorCodeInfo._fromYaml(yaml, aliasFor: aliasFor as String); |
| } else { |
| return AnalyzerErrorCodeInfo._fromYaml(yaml); |
| } |
| } |
| |
| AnalyzerErrorCodeInfo._fromYaml(super.yaml) : super.fromYaml(); |
| } |
| |
| /// Data tables mapping between CFE errors and their corresponding automatically |
| /// generated analyzer errors. |
| class CfeToAnalyzerErrorCodeTables { |
| /// List of CFE errors for which analyzer errors should be automatically |
| /// generated, organized by their `index` property. |
| final List<ErrorCodeInfo?> indexToInfo = []; |
| |
| /// Map whose values are the CFE errors for which analyzer errors should be |
| /// automatically generated, and whose keys are the corresponding analyzer |
| /// error name. (Names are simple identifiers; they are not prefixed by the |
| /// class name `ParserErrorCode`) |
| final Map<String, ErrorCodeInfo> analyzerCodeToInfo = {}; |
| |
| /// Map whose values are the CFE errors for which analyzer errors should be |
| /// automatically generated, and whose keys are the front end error name. |
| final Map<String, ErrorCodeInfo> frontEndCodeToInfo = {}; |
| |
| /// Map whose keys are the CFE errors for which analyzer errors should be |
| /// automatically generated, and whose values are the corresponding analyzer |
| /// error name. (Names are simple identifiers; they are not prefixed by the |
| /// class name `ParserErrorCode`) |
| final Map<ErrorCodeInfo, String> infoToAnalyzerCode = {}; |
| |
| /// Map whose keys are the CFE errors for which analyzer errors should be |
| /// automatically generated, and whose values are the front end error name. |
| final Map<ErrorCodeInfo, String> infoToFrontEndCode = {}; |
| |
| CfeToAnalyzerErrorCodeTables._(Map<String, FrontEndErrorCodeInfo> messages) { |
| for (var entry in messages.entries) { |
| var errorCodeInfo = entry.value; |
| var index = errorCodeInfo.index; |
| if (index == null || errorCodeInfo.analyzerCode.length != 1) { |
| continue; |
| } |
| var frontEndCode = entry.key; |
| if (index < 1) { |
| throw ''' |
| $frontEndCode specifies index $index but indices must be 1 or greater. |
| For more information run: |
| dart pkg/front_end/tool/generate_messages.dart |
| '''; |
| } |
| if (indexToInfo.length <= index) { |
| indexToInfo.length = index + 1; |
| } |
| var previousEntryForIndex = indexToInfo[index]; |
| if (previousEntryForIndex != null) { |
| throw 'Index $index used by both ' |
| '${infoToFrontEndCode[previousEntryForIndex]} and $frontEndCode'; |
| } |
| indexToInfo[index] = errorCodeInfo; |
| frontEndCodeToInfo[frontEndCode] = errorCodeInfo; |
| infoToFrontEndCode[errorCodeInfo] = frontEndCode; |
| var analyzerCodeLong = errorCodeInfo.analyzerCode.single; |
| var expectedPrefix = 'ParserErrorCode.'; |
| if (!analyzerCodeLong.startsWith(expectedPrefix)) { |
| throw 'Expected all analyzer error codes to be prefixed with ' |
| '${json.encode(expectedPrefix)}. Found ' |
| '${json.encode(analyzerCodeLong)}.'; |
| } |
| var analyzerCode = analyzerCodeLong.substring(expectedPrefix.length); |
| infoToAnalyzerCode[errorCodeInfo] = analyzerCode; |
| var previousEntryForAnalyzerCode = analyzerCodeToInfo[analyzerCode]; |
| if (previousEntryForAnalyzerCode != null) { |
| throw 'Analyzer code $analyzerCode used by both ' |
| '${infoToFrontEndCode[previousEntryForAnalyzerCode]} and ' |
| '$frontEndCode'; |
| } |
| analyzerCodeToInfo[analyzerCode] = errorCodeInfo; |
| } |
| for (int i = 1; i < indexToInfo.length; i++) { |
| if (indexToInfo[i] == null) { |
| throw 'Indices are not consecutive; no error code has index $i.'; |
| } |
| } |
| } |
| } |