Add documentation for core lints

Change-Id: I3aa040df18982447ec1e3da074547d28838acae9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/344280
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Marya Belanger <mbelanger@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2024-01-05 21:36:10 +00:00 committed by Commit Queue
parent 121d54f1e0
commit e5cbde13b9
6 changed files with 1433 additions and 19 deletions

View file

@ -8765,12 +8765,12 @@ CompileTimeErrorCode:
The analyzer produces this diagnostic when a member of a class is found
that overrides a member from a supertype and the override isn't valid. An
override is valid if all of these are true:
* It allows all of the arguments allowed by the overridden member.
* It doesn't require any arguments that aren't required by the overridden
- It allows all of the arguments allowed by the overridden member.
- It doesn't require any arguments that aren't required by the overridden
member.
* The type of every parameter of the overridden member is assignable to the
- The type of every parameter of the overridden member is assignable to the
corresponding parameter of the override.
* The return type of the override is assignable to the return type of the
- The return type of the override is assignable to the return type of the
overridden member.
#### Example

View file

@ -92,6 +92,17 @@ class DocumentationValidator {
// The code has been replaced but is not yet removed.
'HintCode.DEPRECATED_MEMBER_USE',
// Missing support for example files outside of `lib`.
'LintCode.avoid_relative_lib_imports',
// Missing support for creating an indirect dependency on a package.
'LintCode.depend_on_referenced_packages',
// Missing support for specifying the name of the test file.
'LintCode.file_names',
// The lint does nothing.
'LintCode.package_prefixed_library_names',
// Missing support for YAML files.
'LintCode.secure_pubspec_urls',
//
// The following can't currently be verified because the examples aren't
// Dart code.
@ -149,6 +160,10 @@ class DocumentationValidator {
var errorClass = classEntry.key;
await _validateMessages(errorClass, classEntry.value);
}
for (var classEntry in lintMessages.entries) {
var errorClass = classEntry.key;
await _validateMessages(errorClass, classEntry.value);
}
ErrorClassInfo? errorClassIncludingCfeMessages;
for (var errorClass in errorClasses) {
if (errorClass.includeCfeMessages) {
@ -271,7 +286,6 @@ class DocumentationValidator {
if (errorCodeInfo.isRemoved) {
continue;
}
var docs = parseErrorCodeDocumentation(
'$className.$errorName', errorCodeInfo.documentation);
if (docs != null) {
@ -291,7 +305,11 @@ class DocumentationValidator {
firstExample = exampleSnippets[0];
}
for (int i = 0; i < exampleSnippets.length; i++) {
await _validateSnippet('example', i, exampleSnippets[i]);
_SnippetData snippet = exampleSnippets[i];
if (className == 'LintCode') {
snippet.lintCode = codeName;
}
await _validateSnippet('example', i, snippet);
}
List<_SnippetData> fixesSnippets =
@ -301,6 +319,9 @@ class DocumentationValidator {
if (firstExample != null) {
snippet.auxiliaryFiles.addAll(firstExample.auxiliaryFiles);
}
if (className == 'LintCode') {
snippet.lintCode = codeName;
}
await _validateSnippet('fixes', i, snippet);
}
}
@ -426,6 +447,7 @@ class _SnippetData {
final Map<String, String> auxiliaryFiles;
final List<String> experiments;
final String? languageVersion;
String? lintCode;
_SnippetData(this.content, this.offset, this.length, this.auxiliaryFiles,
this.experiments, this.languageVersion);
@ -457,10 +479,19 @@ class _SnippetTest extends PubPackageResolutionTest {
@override
void setUp() {
super.setUp();
_createAnalysisOptionsFile();
_createAuxiliaryFiles(snippet.auxiliaryFiles);
addTestFile(snippet.content);
}
void _createAnalysisOptionsFile() {
var lintCode = snippet.lintCode;
if (lintCode != null) {
writeTestPackageAnalysisOptionsFile(
AnalysisOptionsFileConfig(lints: [lintCode]));
}
}
void _createAuxiliaryFiles(Map<String, String> auxiliaryFiles) {
var packageConfigBuilder = PackageConfigFileBuilder();
for (String uriStr in auxiliaryFiles.keys) {

View file

@ -10611,12 +10611,12 @@ _The setter '{1}.{0}' ('{2}') isn't a valid override of '{3}.{0}' ('{4}')._
The analyzer produces this diagnostic when a member of a class is found
that overrides a member from a supertype and the override isn't valid. An
override is valid if all of these are true:
* It allows all of the arguments allowed by the overridden member.
* It doesn't require any arguments that aren't required by the overridden
- It allows all of the arguments allowed by the overridden member.
- It doesn't require any arguments that aren't required by the overridden
member.
* The type of every parameter of the overridden member is assignable to the
- The type of every parameter of the overridden member is assignable to the
corresponding parameter of the override.
* The return type of the override is assignable to the return type of the
- The return type of the override is assignable to the return type of the
overridden member.
#### Example

View file

@ -120,7 +120,7 @@ class _ErrorCodeDocumentationParser {
String get line => commentLines[currentLineNumber];
BlockSection computeCurrentBlockSection() {
BlockSection? computeCurrentBlockSection() {
switch (currentSection) {
case '#### Example':
case '#### Examples':
@ -130,7 +130,7 @@ class _ErrorCodeDocumentationParser {
case null:
problem('Code block before section header');
default:
problem('Code block in invalid section ${json.encode(currentSection)}');
return null;
}
}
@ -177,7 +177,7 @@ class _ErrorCodeDocumentationParser {
List<String>? experiments;
assert(line.startsWith('```'));
var fileType = line.substring(3);
if (fileType.isEmpty) {
if (fileType.isEmpty && containingSection != null) {
problem('Code blocks should have a file type, e.g. "```dart"');
}
++currentLineNumber;
@ -189,12 +189,15 @@ class _ErrorCodeDocumentationParser {
problem('Code blocks should end with "```"');
}
++currentLineNumber;
result.add(ErrorCodeDocumentationBlock(codeLines.join('\n'),
containingSection: containingSection,
experiments: experiments ?? const [],
fileType: fileType,
languageVersion: languageVersion,
uri: uri));
if (containingSection != null) {
// Ignore code blocks where they're allowed but aren't checked.
result.add(ErrorCodeDocumentationBlock(codeLines.join('\n'),
containingSection: containingSection,
experiments: experiments ?? const [],
fileType: fileType,
languageVersion: languageVersion,
uri: uri));
}
return;
} else if (line.startsWith('%')) {
if (line.startsWith(languagePrefix)) {

View file

@ -98,6 +98,13 @@ final Map<String, FrontEndErrorCodeInfo> frontEndMessages =
final String frontEndPkgPath =
normalize(join(pkg_root.packageRoot, 'front_end'));
/// 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();
/// Pattern used by the front end to identify placeholders in error message
/// strings.
// TODO(paulberry): share this regexp (and the code for interpreting
@ -202,6 +209,71 @@ Map<String, FrontEndErrorCodeInfo> decodeCfeMessagesYaml(Object? yaml) {
return result;
}
/// Decodes a YAML object (obtained from `pkg/linter/messages.yaml`) into a
/// two-level map of [ErrorCodeInfo], indexed first by class name and then by
/// error name.
Map<String, Map<String, AnalyzerErrorCodeInfo>> decodeLinterMessagesYaml(
Object? yaml) {
Never problem(String message) {
throw 'Problem in pkg/linter/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! Map<Object?, Object?>) {
problem('value associated with error $className.$errorName is not a '
'map');
}
try {
var aliasFor = errorValue['aliasFor'];
if (aliasFor is String) {
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;
}
(result[className] ??= {})[errorName] = AliasErrorCodeInfo(
aliasFor: aliasFor, comment: errorValue['comment'] as String?);
} else {
(result[className] ??= {})[errorName] =
AnalyzerErrorCodeInfo.fromLinterYaml(errorValue);
}
} catch (e) {
problem('while processing $className.$errorName, $e');
}
}
}
return result;
}
/// Loads analyzer messages from the analyzer's `messages.yaml` file.
Map<String, Map<String, AnalyzerErrorCodeInfo>> _loadAnalyzerMessages() {
Object? messagesYaml =
@ -216,6 +288,13 @@ Map<String, FrontEndErrorCodeInfo> _loadFrontEndMessages() {
return decodeCfeMessagesYaml(messagesYaml);
}
/// Loads linter messages from the linters's `messages.yaml` file.
Map<String, Map<String, AnalyzerErrorCodeInfo>> _loadLintMessages() {
Object? messagesYaml =
loadYaml(File(join(linterPkgPath, 'messages.yaml')).readAsStringSync());
return decodeLinterMessagesYaml(messagesYaml);
}
/// Splits [text] on spaces using the given [maxWidth] (and [firstLineWidth] if
/// given).
List<String> _splitText(
@ -292,6 +371,8 @@ class AnalyzerErrorCodeInfo extends ErrorCodeInfo {
super.sharedName,
});
AnalyzerErrorCodeInfo.fromLinterYaml(super.yaml) : super.fromLinterYaml();
AnalyzerErrorCodeInfo.fromYaml(super.yaml) : super.fromYaml();
}
@ -480,6 +561,13 @@ abstract class ErrorCodeInfo {
this.removedIn,
});
/// Decodes an [ErrorCodeInfo] object from its YAML representation.
ErrorCodeInfo.fromLinterYaml(Map<Object?, Object?> yaml)
: this(
documentation: yaml['documentation'] as String?,
hasPublishedDocs: yaml['hasPublishedDocs'] as bool? ?? false,
problemMessage: '');
/// Decodes an [ErrorCodeInfo] object from its YAML representation.
ErrorCodeInfo.fromYaml(Map<Object?, Object?> yaml)
: this(

1292
pkg/linter/messages.yaml Normal file

File diff suppressed because it is too large Load diff