c5cd37e727
Change-Id: I2e55e146495ab8bd59e87cff9915ee84c99da8b3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106200 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
221 lines
7.3 KiB
Dart
221 lines
7.3 KiB
Dart
// Copyright (c) 2019, 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:io';
|
|
|
|
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
|
|
import 'package:analyzer/dart/analysis/results.dart';
|
|
import 'package:analyzer/dart/analysis/session.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/ast/token.dart';
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
import 'package:front_end/src/testing/package_root.dart' as package_root;
|
|
import 'package:path/src/context.dart';
|
|
|
|
/// Generate the file `diagnostics.md` based on the documentation associated
|
|
/// with the declarations of the error codes.
|
|
void main() async {
|
|
Context pathContext = PhysicalResourceProvider.INSTANCE.pathContext;
|
|
String packageRoot = pathContext.normalize(package_root.packageRoot);
|
|
String analyzerPath = pathContext.join(packageRoot, 'analyzer');
|
|
List<String> docPaths = [
|
|
pathContext.join(
|
|
analyzerPath, 'lib', 'src', 'dart', 'error', 'hint_codes.dart'),
|
|
pathContext.join(analyzerPath, 'lib', 'src', 'error', 'codes.dart'),
|
|
];
|
|
String outputPath =
|
|
pathContext.join(analyzerPath, 'tool', 'diagnostics', 'diagnostics.md');
|
|
|
|
DocumentationGenerator generator = DocumentationGenerator(docPaths);
|
|
generator.writeDocumentation(outputPath);
|
|
}
|
|
|
|
/// A class used to generate diagnostic documentation.
|
|
class DocumentationGenerator {
|
|
/// The absolute paths of the files containing the declarations of the error
|
|
/// codes.
|
|
final List<String> docPaths;
|
|
|
|
/// A map from the name of a diagnostic code to the lines of the documentation
|
|
/// for that code.
|
|
Map<String, List<String>> docsByCode = {};
|
|
|
|
/// Initialize a newly created documentation generator.
|
|
DocumentationGenerator(this.docPaths) {
|
|
_extractAllDocs();
|
|
}
|
|
|
|
/// Write the documentation to the file at the given [outputPath].
|
|
void writeDocumentation(String outputPath) async {
|
|
IOSink sink = File(outputPath).openWrite();
|
|
_writeHeader(sink);
|
|
// _writeGlossary(sink);
|
|
_writeDiagnostics(sink);
|
|
await sink.flush();
|
|
await sink.close();
|
|
}
|
|
|
|
/// Return a version of the [text] in which characters that have special
|
|
/// meaning in markdown have been escaped.
|
|
String _escape(String text) {
|
|
return text.replaceAll('_', '\\_');
|
|
}
|
|
|
|
/// Extract documentation from all of the files containing the definitions of
|
|
/// diagnostics.
|
|
void _extractAllDocs() {
|
|
AnalysisContextCollection collection = new AnalysisContextCollection(
|
|
includedPaths: docPaths,
|
|
resourceProvider: PhysicalResourceProvider.INSTANCE);
|
|
for (String docPath in docPaths) {
|
|
_extractDocs(_parse(collection, docPath));
|
|
}
|
|
}
|
|
|
|
/// Extract documentation from the given [field] declaration.
|
|
List<String> _extractDoc(FieldDeclaration field) {
|
|
Token comments = field.firstTokenAfterCommentAndMetadata.precedingComments;
|
|
if (comments == null) {
|
|
return null;
|
|
}
|
|
List<String> docs = [];
|
|
while (comments != null) {
|
|
String lexeme = comments.lexeme;
|
|
if (lexeme.startsWith('// TODO')) {
|
|
break;
|
|
} else if (lexeme.startsWith('// ')) {
|
|
docs.add(lexeme.substring(3));
|
|
} else if (lexeme == '//') {
|
|
docs.add('');
|
|
}
|
|
comments = comments.next;
|
|
}
|
|
if (docs.isEmpty) {
|
|
return null;
|
|
}
|
|
return docs;
|
|
}
|
|
|
|
/// Extract documentation from the file that was parsed to produce the given
|
|
/// [result].
|
|
void _extractDocs(ParsedUnitResult result) {
|
|
CompilationUnit unit = result.unit;
|
|
for (CompilationUnitMember declaration in unit.declarations) {
|
|
if (declaration is ClassDeclaration) {
|
|
for (ClassMember member in declaration.members) {
|
|
if (member is FieldDeclaration) {
|
|
List<String> docs = _extractDoc(member);
|
|
if (docs != null) {
|
|
VariableDeclaration variable = member.fields.variables[0];
|
|
String variableName = variable.name.name;
|
|
if (docsByCode.containsKey(variableName)) {
|
|
throw StateError('Duplicate diagnostic code');
|
|
}
|
|
String message =
|
|
((variable.initializer as InstanceCreationExpression)
|
|
.argumentList
|
|
.arguments[1] as StringLiteral)
|
|
.stringValue;
|
|
docs = [
|
|
'### ${variableName.toLowerCase()}',
|
|
'',
|
|
..._split('_${_escape(message)}_'),
|
|
'',
|
|
...docs,
|
|
];
|
|
docsByCode[variableName] = docs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Use the analysis context [collection] to parse the file at the given
|
|
/// [path] and return the result.
|
|
ParsedUnitResult _parse(AnalysisContextCollection collection, String path) {
|
|
AnalysisSession session = collection.contextFor(path).currentSession;
|
|
if (session == null) {
|
|
throw new StateError('No session for "$path"');
|
|
}
|
|
ParsedUnitResult result = session.getParsedUnit(path);
|
|
if (result.state != ResultState.VALID) {
|
|
throw new StateError('Unable to parse "$path"');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Split the [message] into multiple lines, each of which is less than 80
|
|
/// characters long.
|
|
List<String> _split(String message) {
|
|
// This uses a brute force approach because we don't expect to have messages
|
|
// that need to be split more than once.
|
|
int length = message.length;
|
|
if (length <= 80) {
|
|
return [message];
|
|
}
|
|
int endIndex = message.lastIndexOf(' ', 80);
|
|
if (endIndex < 0) {
|
|
return [message];
|
|
}
|
|
return [message.substring(0, endIndex), message.substring(endIndex + 1)];
|
|
}
|
|
|
|
/// Write the documentation for all of the diagnostics.
|
|
void _writeDiagnostics(IOSink sink) {
|
|
sink.write('''
|
|
|
|
## Diagnostics
|
|
|
|
The analyzer produces the following diagnostics for code that
|
|
doesn't conform to the language specification or
|
|
that might work in unexpected ways.
|
|
''');
|
|
List<String> errorCodes = docsByCode.keys.toList();
|
|
errorCodes.sort();
|
|
for (String errorCode in errorCodes) {
|
|
List<String> docs = docsByCode[errorCode];
|
|
sink.writeln();
|
|
for (String line in docs) {
|
|
sink.writeln(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
// /// Write the glossary.
|
|
// void _writeGlossary(IOSink sink) {
|
|
// sink.write('''
|
|
//
|
|
//## Glossary
|
|
//
|
|
//This page uses the following terms.
|
|
//
|
|
//### Potentially non-nullable
|
|
//
|
|
//A type is _potentially non-nullable_ if it's either explicitly non-nullable or
|
|
//if it's a type parameter. The latter case is included because the actual runtime
|
|
//type might be non-nullable.
|
|
//''');
|
|
// }
|
|
|
|
/// Write the header of the file.
|
|
void _writeHeader(IOSink sink) {
|
|
sink.write('''
|
|
---
|
|
title: Diagnostics
|
|
description: Details for diagnostics produced by the Dart analyzer.
|
|
---
|
|
{%- comment %}
|
|
WARNING: Do NOT EDIT this file directly. It is autogenerated by the script in
|
|
`pkg/analyzer/tool/diagnostics/generate.dart` in the sdk repository.
|
|
{% endcomment -%}
|
|
|
|
This page lists diagnostic messages produced by the Dart analyzer,
|
|
with details about what those messages mean and how you can fix your code.
|
|
For more information about the analyzer, see
|
|
[Customizing static analysis](/guides/language/analysis-options).
|
|
''');
|
|
}
|
|
}
|