Ban unresolved dartdoc directives from HTML output (#62167)
This commit is contained in:
parent
2273961720
commit
e17a721951
@ -11,6 +11,8 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import 'dartdoc_checker.dart';
|
||||||
|
|
||||||
const String kDocsRoot = 'dev/docs';
|
const String kDocsRoot = 'dev/docs';
|
||||||
const String kPublishRoot = '$kDocsRoot/doc';
|
const String kPublishRoot = '$kDocsRoot/doc';
|
||||||
const String kSnippetsRoot = 'dev/snippets';
|
const String kSnippetsRoot = 'dev/snippets';
|
||||||
@ -222,6 +224,7 @@ Future<void> main(List<String> arguments) async {
|
|||||||
exit(exitCode);
|
exit(exitCode);
|
||||||
|
|
||||||
sanityCheckDocs();
|
sanityCheckDocs();
|
||||||
|
checkForUnresolvedDirectives('$kPublishRoot/api');
|
||||||
|
|
||||||
createIndexAndCleanup();
|
createIndexAndCleanup();
|
||||||
}
|
}
|
||||||
|
127
dev/tools/dartdoc_checker.dart
Normal file
127
dev/tools/dartdoc_checker.dart
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. 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:path/path.dart' as path;
|
||||||
|
|
||||||
|
/// Scans the dartdoc HTML output in the provided `htmlOutputPath` for
|
||||||
|
/// unresolved dartdoc directives (`{@foo x y}`).
|
||||||
|
///
|
||||||
|
/// Dartdoc usually replaces those directives with other content. However,
|
||||||
|
/// if the directive is misspelled (or contains other errors) it is placed
|
||||||
|
/// verbatim into the HTML output. That's not desirable and this check verifies
|
||||||
|
/// that no directives appear verbatim in the output by checking that the
|
||||||
|
/// string `{@` does not appear in the HTML output outside of <code> sections.
|
||||||
|
///
|
||||||
|
/// The string `{@` is allowed in <code> sections, because those may contain
|
||||||
|
/// sample code where the sequence is perfectly legal, e.g. for required named
|
||||||
|
/// parameters of a method:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// void foo({@required int bar});
|
||||||
|
/// ```
|
||||||
|
void checkForUnresolvedDirectives(String htmlOutputPath) {
|
||||||
|
final Directory dartDocDir = Directory(htmlOutputPath);
|
||||||
|
if (!dartDocDir.existsSync()) {
|
||||||
|
throw Exception('Directory with dartdoc output (${dartDocDir.path}) does not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes sure that the path we were given contains some of the expected
|
||||||
|
// libraries and HTML files.
|
||||||
|
final List<String> canaryLibraries = <String>[
|
||||||
|
'animation',
|
||||||
|
'cupertino',
|
||||||
|
'material',
|
||||||
|
'widgets',
|
||||||
|
'rendering',
|
||||||
|
'flutter_driver',
|
||||||
|
];
|
||||||
|
final List<String> canaryFiles = <String>[
|
||||||
|
'Widget-class.html',
|
||||||
|
'Material-class.html',
|
||||||
|
'Canvas-class.html',
|
||||||
|
];
|
||||||
|
|
||||||
|
print('Scanning for unresolved dartdoc directives...');
|
||||||
|
|
||||||
|
final List<FileSystemEntity> toScan = dartDocDir.listSync();
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while (toScan.isNotEmpty) {
|
||||||
|
final FileSystemEntity entity = toScan.removeLast();
|
||||||
|
if (entity is File) {
|
||||||
|
if (path.extension(entity.path) != '.html') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
canaryFiles.remove(path.basename(entity.path));
|
||||||
|
|
||||||
|
// TODO(goderbauer): Remove this exception when https://github.com/dart-lang/dartdoc/issues/2272 is fixed.
|
||||||
|
if (entity.path.endsWith('-class.html') || entity.path.endsWith('-library.html') ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
count += _scanFile(entity);
|
||||||
|
} else if (entity is Directory) {
|
||||||
|
canaryLibraries.remove(path.basename(entity.path));
|
||||||
|
toScan.addAll(entity.listSync());
|
||||||
|
} else {
|
||||||
|
throw Exception('$entity is neither file nor directory.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canaryLibraries.isNotEmpty) {
|
||||||
|
throw Exception('Did not find docs for the following libraries: ${canaryLibraries.join(', ')}.');
|
||||||
|
}
|
||||||
|
if (canaryFiles.isNotEmpty) {
|
||||||
|
throw Exception('Did not find docs for the following files: ${canaryFiles.join(', ')}.');
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
throw Exception('Found $count unresolved dartdoc directives (see log above).');
|
||||||
|
}
|
||||||
|
print('No unresolved dartdoc directives detected.');
|
||||||
|
}
|
||||||
|
|
||||||
|
int _scanFile(File file) {
|
||||||
|
assert(path.extension(file.path) == 'html');
|
||||||
|
Iterable<String> matches = _pattern.allMatches(file.readAsStringSync())
|
||||||
|
.map((RegExpMatch m ) => m.group(0));
|
||||||
|
|
||||||
|
// TODO(goderbauer): Remove this exception when https://github.com/dart-lang/dartdoc/issues/1945 is fixed.
|
||||||
|
matches = matches
|
||||||
|
.where((String m) => m != '{@inject-html}')
|
||||||
|
.where((String m) => m != '{@end-inject-html}');
|
||||||
|
|
||||||
|
if (matches.isNotEmpty) {
|
||||||
|
stderr.writeln('Found unresolved dartdoc directives in ${file.path}:');
|
||||||
|
for (final String match in matches) {
|
||||||
|
stderr.writeln(' $match');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches all `{@` that are not within `<code></code>` sections.
|
||||||
|
//
|
||||||
|
// This regex may lead to false positives if the docs ever contain nested tags
|
||||||
|
// inside <code> sections. Since we currently don't do that, doing the matching
|
||||||
|
// with a regex is a lot faster than using an HTML parser to strip out the
|
||||||
|
// <code> sections.
|
||||||
|
final RegExp _pattern = RegExp(r'({@[^}\n]*}?)(?![^<>]*</code)');
|
||||||
|
|
||||||
|
// Usually, the checker is invoked directly from `dartdoc.dart`. Main method
|
||||||
|
// is included for convenient local runs without having to regenerate
|
||||||
|
// the dartdocs every time.
|
||||||
|
//
|
||||||
|
// Provide the path to the dartdoc HTML output as an argument when running the
|
||||||
|
// program.
|
||||||
|
void main(List<String> args) {
|
||||||
|
if (args.length != 1) {
|
||||||
|
throw Exception('Must provide the path to the dartdoc HTML output as argument.');
|
||||||
|
}
|
||||||
|
if (!Directory(args.single).existsSync()) {
|
||||||
|
throw Exception('The dartdoc HTML output directory ${args.single} does not exist.');
|
||||||
|
}
|
||||||
|
checkForUnresolvedDirectives(args.single);
|
||||||
|
}
|
@ -562,7 +562,7 @@ class InkResponse extends StatelessWidget {
|
|||||||
/// {@macro flutter.widgets.Focus.focusNode}
|
/// {@macro flutter.widgets.Focus.focusNode}
|
||||||
final FocusNode focusNode;
|
final FocusNode focusNode;
|
||||||
|
|
||||||
/// {@template flutter.widgets.Focus.canRequestFocus}
|
/// {@macro flutter.widgets.Focus.canRequestFocus}
|
||||||
final bool canRequestFocus;
|
final bool canRequestFocus;
|
||||||
|
|
||||||
/// The rectangle to use for the highlight effect and for clipping
|
/// The rectangle to use for the highlight effect and for clipping
|
||||||
|
@ -94,6 +94,7 @@ abstract class GradientTransform {
|
|||||||
/// transform: GradientRotation(math.pi/4),
|
/// transform: GradientRotation(math.pi/4),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
|
/// {@end-tool}
|
||||||
@immutable
|
@immutable
|
||||||
class GradientRotation extends GradientTransform {
|
class GradientRotation extends GradientTransform {
|
||||||
/// Constructs a [GradientRotation] for the specified angle.
|
/// Constructs a [GradientRotation] for the specified angle.
|
||||||
|
@ -652,11 +652,6 @@ class EditableText extends StatefulWidget {
|
|||||||
/// text.
|
/// text.
|
||||||
///
|
///
|
||||||
/// Defaults to the ambient [Directionality], if any.
|
/// Defaults to the ambient [Directionality], if any.
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * {@macro flutter.gestures.monodrag.dragStartExample}
|
|
||||||
///
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
final TextDirection textDirection;
|
final TextDirection textDirection;
|
||||||
|
|
||||||
|
@ -457,7 +457,7 @@ class RangeMaintainingScrollPhysics extends ScrollPhysics {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics())
|
/// BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics())
|
||||||
/// ```
|
/// ```
|
||||||
/// (@end-tool}
|
/// {@end-tool}
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
|
@ -43,7 +43,7 @@ class TickerMode extends StatelessWidget {
|
|||||||
|
|
||||||
/// The widget below this widget in the tree.
|
/// The widget below this widget in the tree.
|
||||||
///
|
///
|
||||||
/// {@template flutter.widgets.child}
|
/// {@macro flutter.widgets.child}
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
/// Whether tickers in the given subtree should be enabled or disabled.
|
/// Whether tickers in the given subtree should be enabled or disabled.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user