blob: d6ef172069a2d99068095d7e7b24d9f6c1b4b585 [file] [log] [blame] [edit]
// Copyright (c) 2025, 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.
///
/// Note: to look up an error class by name, use [ErrorClassInfo.byName].
const List<ErrorClassInfo> errorClasses = [
lintCodeInfo,
linterLintCodeInfo,
GeneratedErrorClassInfo(
file: optionCodesFile,
name: 'AnalysisOptionsErrorCode',
type: 'COMPILE_TIME_ERROR',
severity: 'ERROR',
),
GeneratedErrorClassInfo(
file: optionCodesFile,
name: 'AnalysisOptionsWarningCode',
type: 'STATIC_WARNING',
severity: 'WARNING',
),
GeneratedErrorClassInfo(
file: codesFile,
name: 'CompileTimeErrorCode',
type: 'COMPILE_TIME_ERROR',
),
GeneratedErrorClassInfo(
file: scannerErrorFile,
name: 'ScannerErrorCode',
type: 'SYNTACTIC_ERROR',
),
GeneratedErrorClassInfo(
file: codesFile,
name: 'StaticWarningCode',
type: 'STATIC_WARNING',
severity: 'WARNING',
),
GeneratedErrorClassInfo(
file: codesFile,
name: 'WarningCode',
type: 'STATIC_WARNING',
severity: 'WARNING',
),
GeneratedErrorClassInfo(
file: ffiCodesFile,
name: 'FfiCode',
type: 'COMPILE_TIME_ERROR',
),
GeneratedErrorClassInfo(file: hintCodesFile, name: 'HintCode', type: 'HINT'),
GeneratedErrorClassInfo(
file: syntacticErrorsFile,
name: 'ParserErrorCode',
type: 'SYNTACTIC_ERROR',
severity: 'ERROR',
deprecatedSnakeCaseNames: {
'UNEXPECTED_TOKEN', // Referenced by `package:dart_style`.
},
),
GeneratedErrorClassInfo(
file: manifestWarningCodeFile,
name: 'ManifestWarningCode',
type: 'STATIC_WARNING',
severity: 'WARNING',
),
GeneratedErrorClassInfo(
file: pubspecWarningCodeFile,
name: 'PubspecWarningCode',
type: 'STATIC_WARNING',
severity: 'WARNING',
),
GeneratedErrorClassInfo(
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.
''',
),
GeneratedErrorClassInfo(
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 String generatedLintCodesPath = 'linter/lib/src/lint_codes.g.dart';
const hintCodesFile = GeneratedErrorCodeFile(
path: 'analyzer/lib/src/dart/error/hint_codes.g.dart',
parentLibrary: 'package:analyzer/src/dart/error/hint_codes.dart',
);
const lintCodeInfo = ErrorClassInfo(name: 'LintCode');
const lintCodesFile = GeneratedErrorCodeFile(
path: generatedLintCodesPath,
parentLibrary: 'package:linter/src/lint_codes.dart',
);
const linterLintCodeInfo = GeneratedErrorClassInfo(
file: lintCodesFile,
name: 'LinterLintCode',
type: 'LINT',
);
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<AnalyzerCode, AnalyzerErrorCodeInfo> analyzerMessages =
_loadAnalyzerMessages();
/// The path to the `analyzer` package.
final String analyzerPkgPath = normalize(
join(pkg_root.packageRoot, 'analyzer'),
);
/// 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<AnalyzerCode, AnalyzerErrorCodeInfo> lintMessages =
_loadLintMessages();
/// Decodes a YAML object (obtained from a `messages.yaml` file) into a map.
Map<AnalyzerCode, 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 = <AnalyzerCode, 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[AnalyzerCode(
errorClass: ErrorClassInfo.byName(className),
snakeCaseErrorName: 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<AnalyzerCode, AnalyzerErrorCodeInfo> _loadAnalyzerMessages() =>
decodeAnalyzerMessagesYaml(analyzerPkgPath);
/// Loads linter messages from the linter's `messages.yaml` file.
Map<AnalyzerCode, 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;
@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();
}