Add the ability to specify additional imports for doc samples.

Change-Id: I1c9592d4ce66d08aafacd5ae9722defa114c9953
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/214867
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Devon Carew <devoncarew@google.com>
This commit is contained in:
Devon Carew
2021-09-30 16:35:59 +00:00
committed by commit-bot@chromium.org
parent 8c41cc9537
commit bafa71a8ae
8 changed files with 84 additions and 30 deletions
+1 -1
View File
@@ -2063,7 +2063,7 @@ abstract class HttpClientRequest implements IOSink {
/// Using the [IOSink] methods (e.g., [write] and [add]) has no effect after
/// the request has been aborted
///
/// ```dart
/// ```dart import:async
/// HttpClientRequst request = ...
/// request.write();
/// Timer(Duration(seconds: 1), () {
+3 -3
View File
@@ -32,7 +32,7 @@
/// Many methods in the Dart libraries return `Future`s when
/// performing tasks. For example, when binding an `HttpServer`
/// to a host and port, the `bind()` method returns a Future.
/// ```dart
/// ```dart import:io
/// HttpServer.bind('127.0.0.1', 4444)
/// .then((server) => print('${server.isBroadcast}'))
/// .catchError(print);
@@ -60,7 +60,7 @@
/// the stream has finished.
/// Further functionality is provided on [Stream], implemented by calling
/// [Stream.listen] to get the actual data.
/// ```dart
/// ```dart import:io import:convert
/// Stream<List<int>> stream = File('quotes.txt').openRead();
/// stream.transform(utf8.decoder).forEach(print);
/// ```
@@ -72,7 +72,7 @@
///
/// Another common use of streams is for user-generated events
/// in a web app: The following code listens for mouse clicks on a button.
/// ```dart
/// ```dart import:html
/// querySelector('#myButton').onClick.forEach((_) => print('Click.'));
/// ```
/// ## Other resources
+1 -1
View File
@@ -738,7 +738,7 @@ abstract class Future<T> {
///
/// This method is equivalent to:
/// ```dart
/// Future<T> whenComplete(action()) {
/// Future<T> whenComplete(action() {
/// return this.then((v) {
/// var f2 = action();
/// if (f2 is Future) return f2.then((_) => v);
+1 -1
View File
@@ -2059,7 +2059,7 @@ abstract class StreamTransformer<S, T> {
/// [StreamTransformer.bind] API and can be used when the transformation is
/// available as a stream-to-stream function.
///
/// ```dart
/// ```dart import:convert
/// final splitDecoded = StreamTransformer<List<int>, String>.fromBind(
/// (stream) => stream.transform(utf8.decoder).transform(LineSplitter()));
/// ```
+1 -1
View File
@@ -34,7 +34,7 @@
/// as it's read from a file,
/// The second is an instance of [LineSplitter],
/// which splits the data on newline boundaries.
/// ```dart
/// ```dart import:io
/// var lineNumber = 1;
/// var stream = File('quotes.txt').openRead();
///
+2 -2
View File
@@ -111,7 +111,7 @@
/// and listens for the data on the returned web socket.
/// For example, here's a mini server that listens for 'ws' data
/// on a WebSocket:
/// ```dart
/// ```dart import:async
/// runZoned(() async {
/// var server = await HttpServer.bind('127.0.0.1', 4040);
/// server.listen((HttpRequest req) async {
@@ -140,7 +140,7 @@
/// Use [ServerSocket] on the server side and [Socket] on the client.
/// The server creates a listening socket using the `bind()` method and
/// then listens for incoming connections on the socket. For example:
/// ```dart
/// ```dart import:convert
/// ServerSocket.bind('127.0.0.1', 4041)
/// .then((serverSocket) {
/// serverSocket.listen((socket) {
+33 -2
View File
@@ -20,5 +20,36 @@ The tool should be run from the root of the sdk repository.
## Authoring code samples
TODO(devoncarew): Document the conventions for code samples in the dart: libraries
and the tools available to configure them.
### What gets analyzed
This tool will walk all dartdoc api docs looking for code samples in doc comments.
It will analyze any code sample in a `dart` code fence. For example:
> ```dart
> print('hello world!');
> ```
By default, an import for that library is added to the sample being analyzed (i.e.,
`import 'dart:async";`). Additionally, the code sample is automatically embedded in
the body of a simple main() method.
### Excluding code samples from analysis
In order to exclude a code sample from analysis, change it to a plain code fence style:
> ```
> print("I'm not analyzed :(");
> ```
### Specifying additional imports
In order to reference code from other Dart core libraries, you can either explicitly add
the import to the code sample - in-line in the sample - or use a directive on the same
line as the code fence. The directive style looks like:
> ```dart import:async
> print('hello world ${Timer()}');
> ```
Multiple imports can be specified like this if desired (i.e., "```dart import:async import:convert").
+42 -19
View File
@@ -28,8 +28,10 @@ void main(List<String> args) async {
print('Validating the dartdoc code samples from the dart: libraries.');
print('');
print('To run this tool, run `dart tools/verify_docs/bin/verify_docs.dart`.');
print('');
print('For documentation about how to author dart: code samples,'
' see tools/verify_docs/README.md');
print('');
final coreLibraries = args.isEmpty
? libDir.listSync().whereType<Directory>().toList()
@@ -90,7 +92,6 @@ Future<bool> verifyFile(String coreLibName, File file, Directory parent) async {
return error.errorCode.type == ErrorType.SYNTACTIC_ERROR;
}).toList();
if (syntacticErrors.isNotEmpty) {
// todo: have a better failure mode
throw Exception(syntacticErrors);
}
@@ -108,21 +109,22 @@ Future<bool> verifyFile(String coreLibName, File file, Directory parent) async {
return visitor.errors.isEmpty;
}
/// todo: doc
/// Visit a compilation unit and collect the list of code samples found in
/// dartdoc comments.
class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
final String coreLibName;
final String filePath;
final LineInfo lineInfo;
final List<CodeSample> samples = [];
final StringBuffer errors = StringBuffer();
ValidateCommentCodeSamplesVisitor(
this.coreLibName,
this.filePath,
this.lineInfo,
);
final List<CodeSample> samples = [];
final StringBuffer errors = StringBuffer();
Future process(ParseStringResult parseResult) async {
// collect code samples
visitCompilationUnit(parseResult.unit);
@@ -135,7 +137,6 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
@override
void visitAnnotatedNode(AnnotatedNode node) {
// todo: ignore (or fail?) doc comments on non-public symbols
_handleDocumentableNode(node);
super.visitAnnotatedNode(node);
}
@@ -158,6 +159,11 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
var offset = text.indexOf(sampleStart);
while (offset != -1) {
// Collect template directives, like "```dart import:async".
final codeFenceSuffix = text.substring(
offset + sampleStart.length, text.indexOf('\n', offset));
final directives = Set.unmodifiable(codeFenceSuffix.trim().split(' '));
offset = text.indexOf('\n', offset) + 1;
final end = text.indexOf(sampleEnd, offset);
@@ -166,13 +172,12 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
List<String> lines = snippet.split('\n');
// TODO(devoncarew): Also look for template directives.
samples.add(
CodeSample(
coreLibName,
lines.map((e) => ' ${cleanDocLine(e)}').join('\n'),
commentLineStart +
coreLibName: coreLibName,
directives: directives,
lineStartOffset: commentLineStart +
text.substring(0, offset - 1).split('\n').length -
1,
),
@@ -183,9 +188,6 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
}
Future validateCodeSample(CodeSample sample) async {
// TODO(devoncarew): Support <!-- template: none --> ?
// TODO(devoncarew): Support <!-- template: main --> ?
final resourceProvider =
OverlayResourceProvider(PhysicalResourceProvider.INSTANCE);
@@ -212,6 +214,12 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
text = "main() async {\n${text.trimRight()}\n}\n";
}
for (final directive
in sample.directives.where((str) => str.startsWith('import:'))) {
final libName = directive.substring('import:'.length);
text = "import 'dart:$libName';\n$text";
}
if (sample.coreLibName != 'internal') {
text = "import 'dart:${sample.coreLibName}';\n$text";
}
@@ -227,7 +235,7 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
modificationStamp: 0,
);
// TODO(devoncarew): refactor to use AnalysisContextCollection to avoid
// TODO(devoncarew): Refactor to use AnalysisContextCollection to avoid
// re-creating analysis contexts.
final result = await resolveFile2(
path: sampleFilePath,
@@ -239,8 +247,9 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
if (result is ResolvedUnitResult) {
// Filter out unused imports, since we speculatively add imports to some
// samples.
var errors =
result.errors.where((e) => e.errorCode != HintCode.UNUSED_IMPORT);
var errors = result.errors.where(
(e) => e.errorCode != HintCode.UNUSED_IMPORT,
);
// Also, don't worry about 'unused_local_variable' and related; this may
// be intentional in samples.
@@ -250,8 +259,16 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
e.errorCode != HintCode.UNUSED_ELEMENT,
);
// Remove warnings about deprecated member use from the same library.
errors = errors.where(
(e) =>
e.errorCode != HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE &&
e.errorCode !=
HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE_WITH_MESSAGE,
);
if (errors.isNotEmpty) {
print('$filePath:${sample.lineStart}: ${errors.length} errors');
print('$filePath:${sample.lineStartOffset}: ${errors.length} errors');
for (final error in errors) {
final location = result.lineInfo.getLocation(error.offset);
@@ -290,10 +307,16 @@ String cleanDocLine(String line) {
class CodeSample {
final String coreLibName;
final Set<String> directives;
final String text;
final int lineStart;
final int lineStartOffset;
CodeSample(this.coreLibName, this.text, this.lineStart);
CodeSample(
this.text, {
required this.coreLibName,
this.directives = const {},
required this.lineStartOffset,
});
}
String _severity(Severity severity) {