mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 09:01:42 +00:00
Allow code samples to *continue* a prior sample.
We have added more examples, and some of them uses the structure of: ````dart /// ```dart /// var x = something; /// ``` /// and then you can also floo the thing /// ``` /// x.floo(...); /// ``` ```` The following chunks of the same example can now be written as: ````dart /// ```dart continued /// x.floo(...); /// ``` ```` Change handling of imports, and introduce a `top` template different from `none`. The `none` template gets nothing for free. The sample must be completely self-contained. Is triggered by the sample containing a `library` declaration, because we can't add anything before that. The `top` template allows top-level declarations, but does introduce automatic imports and "samples can expect" code if the sample doesn't contain `import`s. Is triggered by top-level declarations other than `library`. Also some restructuring of the code to make this feature easier to implement. Change-Id: If2288147face01efad2ad656aa52183cb4c8b3bf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221343 Commit-Queue: Lasse Nielsen <lrn@google.com> Reviewed-by: Devon Carew <devoncarew@google.com>
This commit is contained in:
parent
65a3e2eb4c
commit
44baaf13b6
|
@ -43,7 +43,7 @@ typedef _Hasher<K> = int Function(K object);
|
|||
/// final Map<int, String> planets = HashMap(); // Is a HashMap
|
||||
/// ```
|
||||
/// To add data to a map, use [operator[]=], [addAll] or [addEntries].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets[3] = 'Earth';
|
||||
/// planets.addAll({4: 'Mars'});
|
||||
/// final gasGiants = {6: 'Jupiter', 5: 'Saturn'};
|
||||
|
@ -52,12 +52,12 @@ typedef _Hasher<K> = int Function(K object);
|
|||
/// ```
|
||||
/// To check if the map is empty, use [isEmpty] or [isNotEmpty].
|
||||
/// To find the number of map entries, use [length].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final isEmpty = planets.isEmpty; // false
|
||||
/// final length = planets.length; // 4
|
||||
/// ```
|
||||
/// The [forEach] iterates through all entries of a map.
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.forEach((key, value) {
|
||||
/// print('$key \t $value');
|
||||
/// // 5 Saturn
|
||||
|
@ -67,32 +67,32 @@ typedef _Hasher<K> = int Function(K object);
|
|||
/// });
|
||||
/// ```
|
||||
/// To check whether the map has an entry with a specific key, use [containsKey].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final keyOneExists = planets.containsKey(4); // true
|
||||
/// final keyFiveExists = planets.containsKey(1); // false
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// To check whether the map has an entry with a specific value,
|
||||
/// use [containsValue].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final marsExists = planets.containsValue('Mars'); // true
|
||||
/// final venusExists = planets.containsValue('Venus'); // false
|
||||
/// ```
|
||||
/// To remove an entry with a specific key, use [remove].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final removeValue = planets.remove(5);
|
||||
/// print(removeValue); // Jupiter
|
||||
/// print(planets); // fx {4: Mars, 3: Earth, 5: Saturn}
|
||||
/// ```
|
||||
/// To remove multiple entries at the same time, based on their keys and values,
|
||||
/// use [removeWhere].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.removeWhere((key, value) => key == 5);
|
||||
/// print(planets); // fx {3: Earth, 4: Mars}
|
||||
/// ```
|
||||
/// To conditionally add or modify a value for a specific key, depending on
|
||||
/// whether there already is an entry with that key,
|
||||
/// use [putIfAbsent] or [update].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.update(4, (v) => 'Saturn');
|
||||
/// planets.update(8, (v) => '', ifAbsent: () => 'Neptune');
|
||||
/// planets.putIfAbsent(4, () => 'Another Saturn');
|
||||
|
@ -100,12 +100,12 @@ typedef _Hasher<K> = int Function(K object);
|
|||
/// ```
|
||||
/// To update the values of all keys, based on the existing key and value,
|
||||
/// use [updateAll].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.updateAll((key, value) => 'X');
|
||||
/// print(planets); // fx {8: X, 3: X, 4: X}
|
||||
/// ```
|
||||
/// To remove all entries and empty the map, use [clear].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.clear();
|
||||
/// print(planets); // {}
|
||||
/// print(planets.isEmpty); // true
|
||||
|
@ -152,7 +152,7 @@ abstract class HashMap<K, V> implements Map<K, V> {
|
|||
/// Example:
|
||||
/// ```dart template:expression
|
||||
/// HashMap<int,int>(equals: (int a, int b) => (b - a) % 5 == 0,
|
||||
/// hashCode: (int e) => e % 5);
|
||||
/// hashCode: (int e) => e % 5)
|
||||
/// ```
|
||||
/// This example map does not need an `isValidKey` function to be passed.
|
||||
/// The default function accepts precisely `int` values, which can safely be
|
||||
|
|
|
@ -38,24 +38,24 @@ part of dart.collection;
|
|||
/// final letters = HashSet<String>();
|
||||
/// ```
|
||||
/// To add data to a set, use [add] or [addAll].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// letters.add('A');
|
||||
/// letters.addAll({'B', 'C', 'D'});
|
||||
/// ```
|
||||
/// To check if the set is empty, use [isEmpty] or [isNotEmpty].
|
||||
/// To find the number of elements in the set, use [length].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// print(letters.isEmpty); // false
|
||||
/// print(letters.length); // 4
|
||||
/// print(letters); // fx {A, D, C, B}
|
||||
/// ```
|
||||
/// To check whether the set has an element with a specific value,
|
||||
/// use [contains].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final bExists = letters.contains('B'); // true
|
||||
/// ```
|
||||
/// The [forEach] method calls a function with each element of the set.
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// letters.forEach(print);
|
||||
/// // A
|
||||
/// // D
|
||||
|
@ -63,29 +63,29 @@ part of dart.collection;
|
|||
/// // B
|
||||
/// ```
|
||||
/// To make a copy of the set, use [toSet].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final anotherSet = letters.toSet();
|
||||
/// print(anotherSet); // fx {A, C, D, B}
|
||||
/// ```
|
||||
/// To remove an element, use [remove].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final removedValue = letters.remove('A'); // true
|
||||
/// print(letters); // fx {B, C, D}
|
||||
/// ```
|
||||
/// To remove multiple elements at the same time, use [removeWhere] or
|
||||
/// [removeAll].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// letters.removeWhere((element) => element.startsWith('B'));
|
||||
/// print(letters); // fx {D, C}
|
||||
/// ```
|
||||
/// To removes all elements in this set that do not meet a condition,
|
||||
/// use [retainWhere].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// letters.retainWhere((element) => element.contains('C'));
|
||||
/// print(letters); // {C}
|
||||
/// ```
|
||||
/// To remove all elements and empty the set, use [clear].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// letters.clear();
|
||||
/// print(letters.isEmpty); // true
|
||||
/// print(letters); // {}
|
||||
|
@ -118,7 +118,7 @@ abstract class HashSet<E> implements Set<E> {
|
|||
/// instance of [E], which means that:
|
||||
/// ```dart template:expression
|
||||
/// HashSet<int>(equals: (int e1, int e2) => (e1 - e2) % 5 == 0,
|
||||
/// hashCode: (int e) => e % 5);
|
||||
/// hashCode: (int e) => e % 5)
|
||||
/// ```
|
||||
/// does not need an `isValidKey` argument because it defaults to only
|
||||
/// accepting `int` values which are accepted by both `equals` and `hashCode`.
|
||||
|
|
|
@ -41,20 +41,20 @@ part of dart.collection;
|
|||
/// final planetsByDiameter = {0.949: 'Venus'}; // A new LinkedHashMap
|
||||
/// ```
|
||||
/// To add data to a map, use [operator[]=], [addAll] or [addEntries].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planetsByDiameter[1] = 'Earth';
|
||||
/// planetsByDiameter.addAll({0.532: 'Mars', 11.209: 'Jupiter'});
|
||||
/// ```
|
||||
/// To check if the map is empty, use [isEmpty] or [isNotEmpty].
|
||||
/// To find the number of map entries, use [length].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// print(planetsByDiameter.isEmpty); // false
|
||||
/// print(planetsByDiameter.length); // 4
|
||||
/// print(planetsByDiameter);
|
||||
/// // {0.949: Venus, 1.0: Earth, 0.532: Mars, 11.209: Jupiter}
|
||||
/// ```
|
||||
/// The [forEach] method calls a function for each key/value entry of the map.
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planetsByDiameter.forEach((key, value) {
|
||||
/// print('$key \t $value');
|
||||
/// // 0.949 Venus
|
||||
|
@ -64,44 +64,44 @@ part of dart.collection;
|
|||
/// });
|
||||
/// ```
|
||||
/// To check whether the map has an entry with a specific key, use [containsKey].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final keyOneExists = planetsByDiameter.containsKey(1); // true
|
||||
/// final keyFiveExists = planetsByDiameter.containsKey(5); // false
|
||||
/// ```
|
||||
/// To check whether the map has an entry with a specific value,
|
||||
/// use [containsValue].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final earthExists = planetsByDiameter.containsValue('Earth'); // true
|
||||
/// final saturnExists = planetsByDiameter.containsValue('Saturn'); // false
|
||||
/// ```
|
||||
/// To remove an entry with a specific key, use [remove].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final removedValue = planetsByDiameter.remove(1);
|
||||
/// print(removedValue); // Earth
|
||||
/// print(planetsByDiameter); // {0.949: Venus, 0.532: Mars, 11.209: Jupiter}
|
||||
/// ```
|
||||
/// To remove multiple entries at the same time, based on their keys and values,
|
||||
/// use [removeWhere].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planetsByDiameter.removeWhere((key, value) => key == 0.949);
|
||||
/// print(planetsByDiameter); // {0.532: Mars, 11.209: Jupiter}
|
||||
/// ```
|
||||
/// To conditionally add or modify a value for a specific key, depending on
|
||||
/// whether there already is an entry with that key,
|
||||
/// use [putIfAbsent] or [update].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planetsByDiameter.update(0.949, (v) => 'Venus', ifAbsent: () => 'Venus');
|
||||
/// planetsByDiameter.putIfAbsent(0.532, () => "Another Mars if needed");
|
||||
/// print(planetsByDiameter); // {0.532: Mars, 11.209: Jupiter, 0.949: Venus}
|
||||
/// ```
|
||||
/// To update the values of all keys, based on the existing key and value,
|
||||
/// use [updateAll].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planetsByDiameter.updateAll((key, value) => 'X');
|
||||
/// print(planetsByDiameter); // {0.532: X, 11.209: X, 0.949: X}
|
||||
/// ```
|
||||
/// To remove all entries and empty the map, use [clear].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planetsByDiameter.clear();
|
||||
/// print(planetsByDiameter); // {}
|
||||
/// print(planetsByDiameter.isEmpty); // true
|
||||
|
|
|
@ -44,24 +44,24 @@ part of dart.collection;
|
|||
/// final planets = <String>{}; // LinkedHashSet
|
||||
/// ```
|
||||
/// To add data to a set, use [add] or [addAll].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final uranusAdded = planets.add('Uranus'); // true
|
||||
/// planets.addAll({'Venus', 'Mars', 'Earth', 'Jupiter'});
|
||||
/// print(planets); // {Uranus, Venus, Mars, Earth, Jupiter}
|
||||
/// ```
|
||||
/// To check if the set is empty, use [isEmpty] or [isNotEmpty].
|
||||
/// To find the number of elements in the set, use [length].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// print(planets.isEmpty); // false
|
||||
/// print(planets.length); // 5
|
||||
/// ```
|
||||
/// To check whether the set has an element with a specific value,
|
||||
/// use [contains].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final marsExists = planets.contains('Mars'); // true
|
||||
/// ```
|
||||
/// The [forEach] method calls a function with each element of the set.
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.forEach(print);
|
||||
/// // Uranus
|
||||
/// // Venus
|
||||
|
@ -71,29 +71,29 @@ part of dart.collection;
|
|||
/// ```
|
||||
///
|
||||
/// To make a copy of the set, use [toSet].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final copySet = planets.toSet();
|
||||
/// print(copySet); // {Uranus, Venus, Mars, Earth, Jupiter}
|
||||
/// ```
|
||||
/// To remove an element, use [remove].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final removedValue = planets.remove('Mars'); // Mars
|
||||
/// print(planets); // {Uranus, Venus, Earth, Jupiter}
|
||||
/// ```
|
||||
/// To remove multiple elements at the same time, use [removeWhere] or
|
||||
/// [removeAll].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.removeWhere((element) => element.startsWith('E'));
|
||||
/// print(planets); // {Uranus, Venus, Jupiter}
|
||||
/// ```
|
||||
/// To removes all elements in this set that do not meet a condition,
|
||||
/// use [retainWhere].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.retainWhere((element) => element.contains('Jupiter'));
|
||||
/// print(planets); // {Jupiter}
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// To remove all elements and empty the set, use [clear].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// planets.clear();
|
||||
/// print(planets.isEmpty); // true
|
||||
/// print(planets); // {}
|
||||
|
|
|
@ -532,7 +532,7 @@ class _DoubleLinkedQueueIterator<E> implements Iterator<E> {
|
|||
/// final queue = ListQueue<int>();
|
||||
/// ```
|
||||
/// To add objects to a queue, use [add], [addAll], [addFirst] or[addLast].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// queue.add(5);
|
||||
/// queue.addFirst(0);
|
||||
/// queue.addLast(10);
|
||||
|
@ -541,44 +541,44 @@ class _DoubleLinkedQueueIterator<E> implements Iterator<E> {
|
|||
/// ```
|
||||
/// To check if the queue is empty, use [isEmpty] or [isNotEmpty].
|
||||
/// To find the number of queue entries, use [length].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final isEmpty = queue.isEmpty; // false
|
||||
/// final queueSize = queue.length; // 6
|
||||
/// ```
|
||||
/// To get first or last item from queue, use [first] or [last].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final first = queue.first; // 0
|
||||
/// final last = queue.last; // 3
|
||||
/// ```
|
||||
/// To get item value using index, use [elementAt].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final itemAt = queue.elementAt(2); // 10
|
||||
/// ```
|
||||
/// To convert queue to list, call [toList].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// final numbers = queue.toList();
|
||||
/// print(numbers); // [0, 5, 10, 1, 2, 3]
|
||||
/// ```
|
||||
/// To remove item from queue, call [remove], [removeFirst] or [removeLast].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// queue.remove(10);
|
||||
/// queue.removeFirst();
|
||||
/// queue.removeLast();
|
||||
/// print(queue); // {5, 1, 2}
|
||||
/// ```
|
||||
/// To remove multiple elements at the same time, use [removeWhere].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// queue.removeWhere((element) => element == 1);
|
||||
/// print(queue); // {5, 2}
|
||||
/// ```
|
||||
/// To remove all elements in this queue that do not meet a condition,
|
||||
/// use [retainWhere].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// queue.retainWhere((element) => element < 4);
|
||||
/// print(queue); // {2}
|
||||
/// ```
|
||||
/// To remove all items and empty the set, use [clear].
|
||||
/// ```
|
||||
/// ```dart continued
|
||||
/// queue.clear();
|
||||
/// print(queue.isEmpty); // true
|
||||
/// print(queue); // {}
|
||||
|
|
|
@ -159,7 +159,7 @@ const Null proxy = null;
|
|||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```dart template:none
|
||||
/// ```dart template:top
|
||||
/// @pragma('Tool:pragma-name', [param1, param2, ...])
|
||||
/// class Foo { }
|
||||
///
|
||||
|
|
|
@ -827,7 +827,7 @@ abstract class NativeApi {
|
|||
/// Annotation to be used for marking an external function as FFI native.
|
||||
///
|
||||
/// Example:
|
||||
///```dart template:none
|
||||
///```dart template:top
|
||||
/// @FfiNative<Int64 Function(Int64, Int64)>('FfiNative_Sum', isLeaf:true)
|
||||
/// external int sum(int a, int b);
|
||||
///```
|
||||
|
|
|
@ -644,7 +644,7 @@ const Object sentinelValue = const SentinelValue(0);
|
|||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```dart template:none
|
||||
/// ```dart template:top
|
||||
/// class Two<A, B> {}
|
||||
///
|
||||
/// print(extractTypeArguments<List>(<int>[], <T>() => new Set<T>()));
|
||||
|
@ -658,7 +658,7 @@ const Object sentinelValue = const SentinelValue(0);
|
|||
/// The type argument T is important to choose which specific type parameter
|
||||
/// list in [instance]'s type hierarchy is being extracted. Consider:
|
||||
///
|
||||
/// ```dart template:none
|
||||
/// ```dart template:top
|
||||
/// class A<T> {}
|
||||
/// class B<T> {}
|
||||
///
|
||||
|
|
|
@ -245,7 +245,7 @@ class NullRejectionException implements Exception {
|
|||
|
||||
/// Converts a JavaScript Promise to a Dart [Future].
|
||||
///
|
||||
/// ```dart template:none
|
||||
/// ```dart template:top
|
||||
/// @JS()
|
||||
/// external Promise<num> get threePromise; // Resolves to 3
|
||||
///
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
## Whats' this?
|
||||
## What’s this?
|
||||
|
||||
A tool to validate the documentation comments for the `dart:` libraries.
|
||||
|
||||
## Running the tool
|
||||
|
||||
To validate all the dart: libraries, run:
|
||||
To validate all the `dart:` libraries, run:
|
||||
|
||||
```
|
||||
dart tools/verify_docs/bin/verify_docs.dart
|
||||
```
|
||||
|
||||
Or to validate an individual library (async, collection, js_util, ...), run:
|
||||
Or to validate an individual library (async, collection, js_util, ...), run either of:
|
||||
|
||||
```
|
||||
dart tools/verify_docs/bin/verify_docs.dart sdk/lib/<lib-name>
|
||||
dart tools/verify_docs/bin/verify_docs.dart dart:<lib-name>
|
||||
```
|
||||
|
||||
The tool should be run from the root of the sdk repository.
|
||||
|
@ -22,68 +23,104 @@ The tool should be run from the root of the sdk repository.
|
|||
|
||||
### What gets analyzed
|
||||
|
||||
This tool will walk all dartdoc api docs looking for code samples in doc comments.
|
||||
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!');
|
||||
> ```
|
||||
> ````dart
|
||||
> /// ```dart
|
||||
> /// print('hello world!');
|
||||
> /// ```
|
||||
> ````
|
||||
|
||||
By default, an import for that library is added to the sample being analyzed (i.e.,
|
||||
`import 'dart:async";`).
|
||||
By default, an import for that library is added to the sample being analyzed, e.g., `import 'dart:async";`.
|
||||
|
||||
### 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").
|
||||
> ````dart
|
||||
> /// ```
|
||||
> /// print("I'm not analyzed :(");
|
||||
> /// ```
|
||||
> ````
|
||||
|
||||
### Specifying templates
|
||||
|
||||
The analysis tool can inject the code sample into a template before analyzing the
|
||||
sample. This allows the author to focus on the import parts of the API being
|
||||
sample. This allows the author to focus on the important parts of the API being
|
||||
documented with less boilerplate in the generated docs.
|
||||
|
||||
The template includes an automatic import of the library containing the example, so an example in, say, the documentation of `StreamController.add` would have `dart:async` imported automatically.
|
||||
|
||||
The tool will try and automatically detect the right template to use based on
|
||||
code patterns within the sample itself. In order to explicitly indicate which template
|
||||
to use, you can specify it as part of the code fence line. For example:
|
||||
|
||||
> ```dart template:main
|
||||
> print('hello world ${Timer()}');
|
||||
> ```dart
|
||||
> /// ```dart template:main
|
||||
> /// print('hello world ${Timer()}');
|
||||
> /// ```
|
||||
> ```
|
||||
|
||||
The three current templates are:
|
||||
- `none`: do not wrap the code sample in any template
|
||||
- `main`: wrap the code sample in a simple main() method
|
||||
- `expression`: wrap the code sample in a statement within a main() method
|
||||
The current templates are:
|
||||
|
||||
For most code sample, the auto-detection code will select `template:main` or
|
||||
- `none`: Do not wrap the code sample in any template, including no imports.
|
||||
- `top`: The code sample is top level code, preceded only by imports.
|
||||
- `main`: The code sample is one or more statements in a simple asynchronous `main()` function.
|
||||
- `expression`: The code sample is an expression within a simple asynchronous `main()` method.
|
||||
|
||||
For most code samples, the auto-detection code will select `template:main` or
|
||||
`template:expression`.
|
||||
|
||||
If the example contains any `library` declarations, the template becomes `none`.
|
||||
|
||||
### Specifying additional imports
|
||||
|
||||
If your example contains any `library`, the default import of the current library is omitted. To avoid that, you can declare extra automatic imports in the code fence like:
|
||||
|
||||
> ````dart
|
||||
> /// ```dart import:async
|
||||
> /// print('hello world ${Timer()}');
|
||||
> /// ```
|
||||
> ````
|
||||
|
||||
Multiple imports can be specified like this if desired, e.g., "```` ```dart import:async import:convert````".
|
||||
|
||||
Does not work if combined with `template:none`, whether the `none` template is specified explicitly or auto-detected.
|
||||
|
||||
### Splitting examples
|
||||
|
||||
Some examples may be split into separate code blocks, but should be seen
|
||||
as continuing the same running example.
|
||||
|
||||
If the following code blocks are marked as `continued` as shown below, they
|
||||
are included into the previous code block instead of being treated as a new
|
||||
example.
|
||||
|
||||
> ````dart
|
||||
> /// ```dart
|
||||
> /// var list = [1, 2, 3];
|
||||
> /// ```
|
||||
> /// And then you can also do the following:
|
||||
> /// ```dart continued
|
||||
> /// list.forEach(print);
|
||||
> /// ```
|
||||
> ````
|
||||
|
||||
A `continued` code block cannot have any other flags in the fence.
|
||||
|
||||
### Including additional code for analysis
|
||||
|
||||
You can declare code that should be included in the analysis but not shown in
|
||||
the API docs by adding a comment "// Examples can assume:" to the file (usually
|
||||
at the top of the file, after the imports), following by one or more
|
||||
commented-out lines of code. That code is included verbatim in the analysis. For
|
||||
example:
|
||||
commented-out lines of code. That code is included verbatim in the analysis, at top-level after the automatic imports. Does not work with `template:none`.
|
||||
|
||||
For example:
|
||||
|
||||
```dart
|
||||
// Examples can assume:
|
||||
// final BuildContext context;
|
||||
// final String userAvatarUrl;
|
||||
```
|
||||
|
||||
|
|
226
tools/verify_docs/bin/verify_docs.dart
Normal file → Executable file
226
tools/verify_docs/bin/verify_docs.dart
Normal file → Executable file
|
@ -2,6 +2,8 @@
|
|||
// 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.
|
||||
|
||||
// Read the ../README.md file for the recognized syntax.
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
|
||||
|
@ -20,10 +22,10 @@ import 'package:analyzer/src/error/codes.dart';
|
|||
import 'package:analyzer/src/util/comment.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
final libDir = Directory(path.join('sdk', 'lib'));
|
||||
void main(List<String> args) async {
|
||||
final libDir = Directory('sdk/lib');
|
||||
if (!libDir.existsSync()) {
|
||||
print('Please run this tool from the root of the sdk repo.');
|
||||
print('Please run this tool from the root of the sdk repository.');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
@ -37,7 +39,7 @@ void main(List<String> args) async {
|
|||
|
||||
final coreLibraries = args.isEmpty
|
||||
? libDir.listSync().whereType<Directory>().toList()
|
||||
: args.map((arg) => Directory(arg)).toList();
|
||||
: args.map(parseArg).toList();
|
||||
coreLibraries.sort((a, b) => a.path.compareTo(b.path));
|
||||
|
||||
// Skip some dart: libraries.
|
||||
|
@ -172,9 +174,9 @@ 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(' '));
|
||||
final codeFenceSuffix = text
|
||||
.substring(offset + sampleStart.length, text.indexOf('\n', offset))
|
||||
.trim();
|
||||
|
||||
offset = text.indexOf('\n', offset) + 1;
|
||||
final end = text.indexOf(sampleEnd, offset);
|
||||
|
@ -183,73 +185,128 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
|
|||
snippet = snippet.substring(0, snippet.lastIndexOf('\n'));
|
||||
|
||||
List<String> lines = snippet.split('\n');
|
||||
|
||||
var startLineNumber = commentLineStart +
|
||||
text.substring(0, offset - 1).split('\n').length -
|
||||
1;
|
||||
if (codeFenceSuffix == "continued") {
|
||||
if (samples.isEmpty) {
|
||||
throw "Continued code block without previous code";
|
||||
}
|
||||
samples.last = samples.last.append(lines, startLineNumber);
|
||||
} else {
|
||||
final directives = Set.unmodifiable(codeFenceSuffix.split(' '));
|
||||
samples.add(
|
||||
CodeSample(
|
||||
lines.map((e) => ' ${cleanDocLine(e)}').join('\n'),
|
||||
[for (var e in lines) ' ${cleanDocLine(e)}'],
|
||||
coreLibName: coreLibName,
|
||||
directives: directives,
|
||||
lineStartOffset: commentLineStart +
|
||||
text.substring(0, offset - 1).split('\n').length -
|
||||
1,
|
||||
lineStartOffset: startLineNumber,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
offset = text.indexOf(sampleStart, offset);
|
||||
}
|
||||
}
|
||||
|
||||
Future validateCodeSample(CodeSample sample) async {
|
||||
var text = sample.text;
|
||||
final lines = sample.text.split('\n').map((l) => l.trim()).toList();
|
||||
// RegExp detecting various top-level declarations or `main(`.
|
||||
//
|
||||
// If the top-level declaration is `library` or `import`,
|
||||
// then match 1 (`libdecl`) will be non-null.
|
||||
// This is a sign that no auto-imports should be added.
|
||||
//
|
||||
// If an import declaration is included in the sample, no
|
||||
// assumed-declarations are added.
|
||||
// Use the `import:foo` template to import other `dart:` libraries
|
||||
// instead of writing them explicitly to.
|
||||
//
|
||||
// Captures:
|
||||
// 1/libdecl: Non-null if mathcing a `library` declaration.
|
||||
// 2: Internal use, quote around import URI.
|
||||
// 3/importuri: Import URI.
|
||||
final _toplevelDeclarationRE = RegExp(r'^\s*(?:'
|
||||
r'library\b(?<libdecl>)|'
|
||||
r'''import (['"])(?<importuri>.*?)\2|'''
|
||||
r'class\b|mixin\b|enum\b|extension\b|typedef\b|.*\bmain\('
|
||||
r')');
|
||||
|
||||
final hasImports = text.contains("import '") || text.contains('import "');
|
||||
validateCodeSample(CodeSample sample) async {
|
||||
final lines = sample.lines;
|
||||
|
||||
// One of 'none', 'main', or 'expression'.
|
||||
String? template;
|
||||
// The default imports includes the library itself
|
||||
// and any import directives.
|
||||
Set<String> autoImports = sample.imports;
|
||||
|
||||
if (sample.hasTemplateDirective) {
|
||||
template = sample.templateDirective;
|
||||
// One of 'none', 'top, 'main', or 'expression'.
|
||||
String template;
|
||||
|
||||
bool hasImport = false;
|
||||
|
||||
final templateDirective = sample.templateDirective;
|
||||
if (templateDirective != null) {
|
||||
template = templateDirective;
|
||||
} else {
|
||||
// If there's no explicit template, auto-detect one.
|
||||
if (lines.any((line) =>
|
||||
line.startsWith('class ') ||
|
||||
line.startsWith('enum ') ||
|
||||
line.startsWith('extension '))) {
|
||||
template = 'none';
|
||||
} else if (lines
|
||||
.any((line) => line.startsWith('main(') || line.contains(' main('))) {
|
||||
// Scan lines for top-level declarations.
|
||||
bool hasTopDeclaration = false;
|
||||
bool hasLibraryDeclaration = false;
|
||||
for (var line in lines) {
|
||||
var topDeclaration = _toplevelDeclarationRE.firstMatch(line);
|
||||
if (topDeclaration != null) {
|
||||
hasTopDeclaration = true;
|
||||
hasLibraryDeclaration |=
|
||||
(topDeclaration.namedGroup("libdecl") != null);
|
||||
var importDecl = topDeclaration.namedGroup("importuri");
|
||||
if (importDecl != null) {
|
||||
hasImport = true;
|
||||
if (importDecl.startsWith('dart:')) {
|
||||
// Remove explicit imports from automatic imports
|
||||
// to avoid duplicate import warnings.
|
||||
autoImports.remove(importDecl.substring('dart:'.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasLibraryDeclaration) {
|
||||
template = 'none';
|
||||
} else if (hasTopDeclaration) {
|
||||
template = 'top';
|
||||
} else if (lines.length == 1 && !lines.first.trim().endsWith(';')) {
|
||||
// If single line with no trailing `;`, assume expression.
|
||||
template = 'expression';
|
||||
} else {
|
||||
// Otherwise default to `main`.
|
||||
template = 'main';
|
||||
}
|
||||
}
|
||||
|
||||
final assumptions = sampleAssumptions ?? '';
|
||||
var buffer = StringBuffer();
|
||||
|
||||
if (!hasImports) {
|
||||
if (template == 'none') {
|
||||
// just use the sample text as is
|
||||
if (template != 'none') {
|
||||
for (var library in autoImports) {
|
||||
buffer.writeln("import 'dart:$library';");
|
||||
}
|
||||
if (!hasImport) {
|
||||
buffer.write(sampleAssumptions ?? '');
|
||||
}
|
||||
}
|
||||
if (template == 'none' || template == 'top') {
|
||||
buffer.writeAllLines(lines);
|
||||
} else if (template == 'main') {
|
||||
text = "${assumptions}main() async {\n${text.trimRight()}\n}\n";
|
||||
buffer
|
||||
..writeln('void main() async {')
|
||||
..writeAllLines(lines)
|
||||
..writeln('}');
|
||||
} else if (template == 'expression') {
|
||||
text = "${assumptions}main() async {\n${text.trimRight()}\n;\n}\n";
|
||||
assert(lines.length >= 1);
|
||||
buffer
|
||||
..writeln('void main() async =>')
|
||||
..writeAllLines(lines.take(lines.length - 1))
|
||||
..writeln("${lines.last.trimRight()};");
|
||||
} else {
|
||||
throw 'unexpected template directive: $template';
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
final text = buffer.toString();
|
||||
|
||||
final result = await analysisHelper.resolveFile(text);
|
||||
|
||||
|
@ -311,8 +368,7 @@ class ValidateCommentCodeSamplesVisitor extends GeneralizingAstVisitor {
|
|||
print('');
|
||||
|
||||
// Print out the code sample.
|
||||
print(sample.text
|
||||
.split('\n')
|
||||
print(sample.lines
|
||||
.map((line) =>
|
||||
' >${line.length >= 5 ? line.substring(5) : line.trimLeft()}')
|
||||
.join('\n'));
|
||||
|
@ -337,27 +393,63 @@ String cleanDocLine(String line) {
|
|||
}
|
||||
|
||||
class CodeSample {
|
||||
/// Currently valid template names.
|
||||
static const validTemplates = ['none', 'top', 'main', 'expression'];
|
||||
|
||||
final String coreLibName;
|
||||
final Set<String> directives;
|
||||
final String text;
|
||||
final List<String> lines;
|
||||
final int lineStartOffset;
|
||||
|
||||
CodeSample(
|
||||
this.text, {
|
||||
this.lines, {
|
||||
required this.coreLibName,
|
||||
this.directives = const {},
|
||||
required this.lineStartOffset,
|
||||
});
|
||||
|
||||
String get text => lines.join('\n');
|
||||
|
||||
bool get hasTemplateDirective => templateDirective != null;
|
||||
|
||||
/// The specified template, or `null` if no template is specified.
|
||||
///
|
||||
/// A specified template must be of [validTemplates].
|
||||
String? get templateDirective {
|
||||
const prefix = 'template:';
|
||||
|
||||
String? match = directives.cast<String?>().firstWhere(
|
||||
(directive) => directive!.startsWith(prefix),
|
||||
orElse: () => null);
|
||||
return match == null ? match : match.substring(prefix.length);
|
||||
for (var directive in directives) {
|
||||
if (directive.startsWith(prefix)) {
|
||||
var result = directive.substring(prefix.length);
|
||||
if (!validTemplates.contains(result)) {
|
||||
throw "Invalid template name: $result";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The implicit or explicitly requested imports.
|
||||
Set<String> get imports => {
|
||||
if (coreLibName != 'internal' && coreLibName != 'core') coreLibName,
|
||||
for (var directive in directives)
|
||||
if (directive.startsWith('import:'))
|
||||
directive.substring('import:'.length)
|
||||
};
|
||||
|
||||
/// Creates a new code sample by appending [lines] to this sample.
|
||||
///
|
||||
/// The new sample only differs from this sample in that it has
|
||||
/// more lines appended, first `this.lines`, then a gap of ` //` lines
|
||||
/// and then [lines].
|
||||
CodeSample append(List<String> lines, int lineStartOffset) {
|
||||
var gapSize = lineStartOffset - (this.lineStartOffset + this.lines.length);
|
||||
return CodeSample(
|
||||
[...this.lines, for (var i = 0; i < gapSize; i++) " //", ...lines],
|
||||
coreLibName: coreLibName,
|
||||
directives: directives,
|
||||
lineStartOffset: this.lineStartOffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,3 +514,35 @@ class AnalysisHelper {
|
|||
return await analysisSession.getResolvedUnit(samplePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to make things easier to read.
|
||||
extension on StringBuffer {
|
||||
/// Write every line, right-trimmed, of [lines] with a newline after.
|
||||
void writeAllLines(Iterable<String> lines) {
|
||||
for (var line in lines) {
|
||||
this.writeln(line.trimRight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Interprets [arg] as directory containing a platform library.
|
||||
///
|
||||
/// If [arg] is `dart:foo`, the directory is the default directory for
|
||||
/// the `dart:foo` library source.
|
||||
/// Otherwise, if [arg] is a directory (relative to the current directory)
|
||||
/// which exists, that is the result.
|
||||
/// Otherwise, if [arg] is the name of a platform library,
|
||||
/// like `foo` where `dart:foo` is a platform library,
|
||||
/// the result is the default directory for that library's source.
|
||||
/// Otherwise it's treated as a directory relative to the current directory,
|
||||
/// which doesn't exist (but that's what the error will refer to).
|
||||
Directory parseArg(String arg) {
|
||||
if (arg.startsWith('dart:')) {
|
||||
return Directory(path.join(libDir.path, arg.substring('dart:'.length)));
|
||||
}
|
||||
var dir = Directory(arg);
|
||||
if (dir.existsSync()) return dir;
|
||||
var relDir = Directory(path.join(libDir.path, arg));
|
||||
if (relDir.existsSync()) return relDir;
|
||||
return dir;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue