From e17a721951f684447bab83603bc60732246e08ae Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 28 Jul 2020 15:26:33 -0700 Subject: [PATCH] Ban unresolved dartdoc directives from HTML output (#62167) --- dev/tools/dartdoc.dart | 3 + dev/tools/dartdoc_checker.dart | 127 ++++++++++++++++++ .../flutter/lib/src/material/ink_well.dart | 2 +- .../flutter/lib/src/painting/gradient.dart | 1 + .../lib/src/widgets/editable_text.dart | 5 - .../lib/src/widgets/scroll_physics.dart | 2 +- .../lib/src/widgets/ticker_provider.dart | 2 +- 7 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 dev/tools/dartdoc_checker.dart diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart index c13f92503a..18bafdf9fa 100644 --- a/dev/tools/dartdoc.dart +++ b/dev/tools/dartdoc.dart @@ -11,6 +11,8 @@ import 'package:intl/intl.dart'; import 'package:path/path.dart' as path; import 'package:process/process.dart'; +import 'dartdoc_checker.dart'; + const String kDocsRoot = 'dev/docs'; const String kPublishRoot = '$kDocsRoot/doc'; const String kSnippetsRoot = 'dev/snippets'; @@ -222,6 +224,7 @@ Future main(List arguments) async { exit(exitCode); sanityCheckDocs(); + checkForUnresolvedDirectives('$kPublishRoot/api'); createIndexAndCleanup(); } diff --git a/dev/tools/dartdoc_checker.dart b/dev/tools/dartdoc_checker.dart new file mode 100644 index 0000000000..9340b40f1a --- /dev/null +++ b/dev/tools/dartdoc_checker.dart @@ -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 sections. +/// +/// The string `{@` is allowed in 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 canaryLibraries = [ + 'animation', + 'cupertino', + 'material', + 'widgets', + 'rendering', + 'flutter_driver', + ]; + final List canaryFiles = [ + 'Widget-class.html', + 'Material-class.html', + 'Canvas-class.html', + ]; + + print('Scanning for unresolved dartdoc directives...'); + + final List 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 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 `` sections. +// +// This regex may lead to false positives if the docs ever contain nested tags +// inside 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 +// sections. +final RegExp _pattern = RegExp(r'({@[^}\n]*}?)(?![^<>]* 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); +} diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index 76cb6295b7..081eb5d511 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart @@ -562,7 +562,7 @@ class InkResponse extends StatelessWidget { /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; - /// {@template flutter.widgets.Focus.canRequestFocus} + /// {@macro flutter.widgets.Focus.canRequestFocus} final bool canRequestFocus; /// The rectangle to use for the highlight effect and for clipping diff --git a/packages/flutter/lib/src/painting/gradient.dart b/packages/flutter/lib/src/painting/gradient.dart index 1dde54ccf6..2eecf9eb80 100644 --- a/packages/flutter/lib/src/painting/gradient.dart +++ b/packages/flutter/lib/src/painting/gradient.dart @@ -94,6 +94,7 @@ abstract class GradientTransform { /// transform: GradientRotation(math.pi/4), /// ); /// ``` +/// {@end-tool} @immutable class GradientRotation extends GradientTransform { /// Constructs a [GradientRotation] for the specified angle. diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index c3a7d3d005..7df514ff59 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -652,11 +652,6 @@ class EditableText extends StatefulWidget { /// text. /// /// Defaults to the ambient [Directionality], if any. - /// - /// See also: - /// - /// * {@macro flutter.gestures.monodrag.dragStartExample} - /// /// {@endtemplate} final TextDirection textDirection; diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index f34f10838e..be4160dd79 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -457,7 +457,7 @@ class RangeMaintainingScrollPhysics extends ScrollPhysics { /// ```dart /// BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()) /// ``` -/// (@end-tool} +/// {@end-tool} /// /// See also: /// diff --git a/packages/flutter/lib/src/widgets/ticker_provider.dart b/packages/flutter/lib/src/widgets/ticker_provider.dart index bad3cc84ac..4ea6115fec 100644 --- a/packages/flutter/lib/src/widgets/ticker_provider.dart +++ b/packages/flutter/lib/src/widgets/ticker_provider.dart @@ -43,7 +43,7 @@ class TickerMode extends StatelessWidget { /// The widget below this widget in the tree. /// - /// {@template flutter.widgets.child} + /// {@macro flutter.widgets.child} final Widget child; /// Whether tickers in the given subtree should be enabled or disabled.